{"openapi":"3.0.3","info":{"title":"Chronos API","description":"\n# Chronos Blockchain Adapter API\n\n> **Seamless Web3 Integration** — Gasless transactions, smart accounts, and invisible blockchain UX for your applications.\n\n---\n\n## Overview\n\nChronos provides a simple REST API for blockchain operations without requiring your users to manage wallets, gas, or private keys.\n\n| Feature | Description |\n|---------|-------------|\n| **Wallet Management** | Create smart accounts for your users automatically |\n| **Token Operations** | Check balances and transfer ERC-20 tokens |\n| **Minting** | Mint tokens with receipt-based verification and client-side signing |\n| **Claims** | Lock tokens on-chain to claim rewards |\n| **Gasless** | All transactions are sponsored — no ETH required |\n\n---\n\n## Quick Start\n\n| Step | Action |\n|------|--------|\n| 1 | Contact Chronos admin to register your application |\n| 2 | Receive your `clientId` and `clientSecret` |\n| 3 | Exchange credentials for a JWT token via `POST /auth/token` |\n| 4 | Call APIs with Bearer token and `X-User-Id` header |\n\n---\n\n## Authentication\n\nAll API calls (except `POST /auth/token`) require a JWT Bearer token.\n\n### Get Token\n```bash\nPOST /api/v1/auth/token\nContent-Type: application/json\n\n{\n  \"clientId\": \"your_client_id\",\n  \"clientSecret\": \"your_client_secret\"\n}\n```\n\n### Make API Calls\n```bash\nAuthorization: Bearer <your_jwt_token>\nX-User-Id: <your_user_identifier>\n```\n\n---\n\n## Headers\n\n| Header | Required | Description |\n|--------|----------|-------------|\n| `Authorization` | Yes | Bearer token from `POST /auth/token` |\n| `X-User-Id` | Yes* | Your user's identifier (maps to a smart account). *Not required for `/auth/token`. |\n| `X-Idempotency-Key` | Recommended | Prevents duplicate processing for write operations (mint, transfer, claim) |\n\n---\n\n## Async Transaction Flow\n\nAll mutating endpoints (`mint/execute`, `wallet/transfer`, `claims/execute`) are **asynchronous**:\n\n1. Submit the operation → receive `202 Accepted` with a `transactionId`\n2. **Primary:** Receive the final status via **webhook** push notification (recommended)\n3. **Safety net:** If no webhook arrives within **15 seconds**, fall back to polling `GET /transactions/{transactionId}`\n\n### Transaction Statuses\n\n| Status | Terminal | Description |\n|--------|----------|-------------|\n| `ACCEPTED` | No | Job queued for processing |\n| `SUBMITTING` | No | Building and signing UserOperation |\n| `SUBMITTED` | No | UserOp submitted on-chain, awaiting confirmation |\n| `CONFIRMED` | Yes | Transaction confirmed on-chain |\n| `FAILED` | Yes | Transaction failed |\n| `REVERTED` | Yes | Transaction reverted on-chain |\n| `EXPIRED` | Yes | Transaction expired before confirmation |\n\n### Polling Recommendations\n\n| Status | Poll Interval |\n|--------|---------------|\n| `ACCEPTED` / `SUBMITTING` | Every 1 second |\n| `SUBMITTED` | Every 2–3 seconds |\n| Terminal statuses | Stop polling |\n\n---\n\n## Error Codes\n\n| Code | HTTP Status | Description |\n|------|-------------|-------------|\n| `MISSING_CREDENTIALS` | 400 | Missing clientId or clientSecret |\n| `UNAUTHORIZED` | 401 | Missing or invalid JWT token |\n| `FORBIDDEN` | 403 | Token lacks required scope |\n| `INSUFFICIENT_BALANCE` | 400 | User doesn't have enough tokens |\n| `RECEIPT_ALREADY_MINTED` | 409 | Receipt ID already used |\n| `ADDRESS_MISMATCH` | 400 | Provided userAddress doesn't match wallet on record |\n| `INVALID_SIGNATURE` | 400 | Client signature verification failed |\n| `QUOTA_EXCEEDED` | 429 | Transaction quota exceeded |\n| `WALLET_ERROR` | 400 | Wallet not found or cannot perform operation |\n| `CONFIG_ERROR` | 500 | Server configuration issue |\n\n---\n\n## Rate Limits\n\n  Rate limits are enforced per client using a sliding window algorithm. Limits vary by endpoint type:\n\n  | Endpoint Type | Limit | Window |\n  |---------------|-------|--------|\n  | General API calls | 100 req | 1 minute |\n  | Minting operations | 10 req | 1 minute |\n  | External/webhook calls | 30 req | 1 minute |\n  | Authentication | 5 req | 5 minutes |\n\n  **Graduated response:** At 80% usage you receive a warning header, at 90% responses are throttled (500ms delay), at 100% requests are blocked with `429`.\n\n  **Response headers** included on every response:\n  - `X-RateLimit-Limit` -- effective limit for the current window\n  - `X-RateLimit-Remaining` -- requests remaining\n  - `X-RateLimit-Reset` -- Unix timestamp when the window resets\n  - `Retry-After` -- seconds to wait (only on `429` responses)\n    ","version":"2.3.0","contact":{"name":"Chronos Support"}},"servers":[{"url":"/","description":"Current environment"}],"tags":[{"name":"Authentication","description":"Obtain access tokens using client credentials"},{"name":"Wallets","description":"Create smart accounts, check balances, and transfer tokens"},{"name":"Minting","description":"Prepare signing data and execute gasless ERC-20 token mints"},{"name":"Claims","description":"Lock tokens on-chain to claim rewards"},{"name":"Transactions","description":"Poll the status of async transaction jobs"},{"name":"Quota","description":"Check remaining transaction quota before operations"},{"name":"Webhooks","description":"View webhook delivery history for your client"}],"paths":{"/api/v1/auth/token":{"post":{"tags":["Authentication"],"summary":"Get Access Token","description":"**As a client application, I need to authenticate so I can make API calls on behalf of my users.**\n\nExchange your `clientId` and `clientSecret` for a JWT access token. This is the **only endpoint that does not require a Bearer token** -- it's your entry point.\n\n**Token lifetime:** Tokens expire after the returned `expiresIn` seconds (typically 1 hour). Cache the token and refresh it before expiry -- avoid requesting a new token on every API call.\n\n**Scopes:** Your token is automatically scoped to the operations your client profile allows (wallet, mint, transfer, claim). Attempting an operation outside your scopes returns `403 Forbidden`.","operationId":"createToken","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"clientId":{"type":"string","description":"Your client ID provided by Chronos admin"},"clientSecret":{"type":"string","description":"Your client secret provided by Chronos admin"}},"required":["clientId","clientSecret"]},"example":{"clientId":"client_abc123def456","clientSecret":"sk_live_xxxxxxxxxxxxxxxxxxxx"}}}},"responses":{"200":{"description":"Access token issued","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"JWT access token to use in Authorization header"},"expiresIn":{"type":"integer","description":"Token lifetime in seconds"}},"required":["token","expiresIn"]},"example":{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","expiresIn":3600}}}},"400":{"description":"Missing required fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"Missing required fields: clientId, clientSecret","code":"MISSING_CREDENTIALS"}}}},"401":{"description":"Invalid credentials or client revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"Invalid credentials","code":"INVALID_SECRET"}}}},"500":{"description":"Token generation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/wallet/create":{"post":{"tags":["Wallets"],"summary":"Create or Retrieve Smart Account","description":"**As a client application, I need to create a blockchain wallet for my user so they can hold and transact tokens -- without them knowing anything about blockchain.**\n\nCreates an ERC-4337 smart account tied to the given `X-User-Id`. If the user already has a wallet, returns the existing address with `isNew: false`. This endpoint is **idempotent** -- safe to call multiple times.\n\n**When to call:** Call this once during user onboarding or lazily before the user's first token operation. The wallet address is deterministic per client + user combination.\n\n**No gas required:** The smart account is a counterfactual deployment -- it's deployed on-chain automatically during the user's first transaction, fully sponsored.","operationId":"createWallet","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XUserId"}],"responses":{"200":{"description":"Wallet created or retrieved","content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string","description":"ERC-4337 smart wallet address"},"isNew":{"type":"boolean","description":"True if the wallet was just created, false if it already existed"}},"required":["address","isNew"]},"example":{"address":"0x1234567890abcdef1234567890abcdef12345678","isNew":true}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Wallet creation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/wallet/balance":{"get":{"tags":["Wallets"],"summary":"Get Token Balance","description":"**As a client application, I need to check my user's token balance so I can display it in my UI or verify sufficient funds before a transfer or claim.**\n\nReturns the ERC-20 token balance for the user's smart account in whole token units (e.g. `\"1000\"` means 1,000 tokens). The balance is read directly from the blockchain.\n\n**Prerequisite:** The user must have a wallet created via `POST /wallet/create`. If no wallet exists, returns `404`.\n\n**Caching:** This is a live on-chain read. For high-frequency UIs, consider caching the result client-side for 5-10 seconds.","operationId":"getBalance","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XUserId"}],"responses":{"200":{"description":"Balance retrieved","content":{"application/json":{"schema":{"type":"object","properties":{"balance":{"type":"string","description":"Token balance in whole units (e.g. \"1000\")"}},"required":["balance"]},"example":{"balance":"1000"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Wallet not found -- call `POST /wallet/create` first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"Wallet not found. Create a wallet first."}}}},"500":{"description":"Failed to get balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/wallet/transfer":{"post":{"tags":["Wallets"],"summary":"Transfer Tokens","description":"**As a client application, I need to let my user send tokens to another address -- for example, transferring rewards to another user or sending tokens to an external wallet.**\n\nInitiates a gasless ERC-20 token transfer from the user's smart wallet to the specified address. This is an **async operation** -- returns `202 Accepted` with a `transactionId`.\n\n**Amount format:** Pass the amount in whole token units (e.g. `\"100\"` for 100 tokens). The API handles wei conversion internally.\n\n**Idempotency:** Use the `X-Idempotency-Key` header to prevent duplicate transfers if your system retries on timeout. The same key returns the original `transactionId` without re-executing.","operationId":"transferTokens","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XUserId"},{"$ref":"#/components/parameters/XIdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","description":"Destination Ethereum address. Also accepts `toAddress` as an alias."},"amount":{"type":"string","description":"Amount in whole token units (e.g. \"100\" for 100 tokens)"}},"required":["to","amount"]},"example":{"to":"0xRecipientAddress1234567890abcdef12345678","amount":"100"}}}},"responses":{"202":{"description":"Transfer accepted -- poll status via `GET /transactions/{transactionId}`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionAccepted"},"example":{"transactionId":"tx_abc123def456","status":"ACCEPTED"}}}},"400":{"description":"Validation error (missing fields, insufficient balance, wallet error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"Insufficient balance","code":"INSUFFICIENT_BALANCE"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/mint/prepare":{"post":{"tags":["Minting"],"summary":"Prepare Mint Signing Data","description":"**As a client application, I need to get the on-chain parameters required to sign a mint authorization before executing it.**\n\nThis is **step 1 of the two-step mint flow**. Returns `contractAddress`, `chainId`, `expiryBlock`, and `nonce` -- the values your backend needs to construct and sign the mint authorization message.\n\n**Client-Side Signing Flow:**\n1. Call this endpoint with `receiptId` and `userAddress`\n2. Construct: `messageHash = keccak256(abi.encode(contractAddress, chainId, userAddress, receiptId, amount, expiryBlock, nonce))`\n3. Sign with your registered signer private key\n4. Submit signature + all parameters to `POST /mint/execute`\n\n**Why two steps?** This pattern ensures only your authorized backend can approve mints. The blockchain contract verifies the signature matches your registered signer address before allowing the mint.","operationId":"prepareMint","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XUserId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"receiptId":{"type":"string","description":"Unique receipt identifier from your system (1-256 chars, alphanumeric + dashes/underscores)"},"userAddress":{"type":"string","description":"The user's smart wallet address (must match the wallet on record from `POST /wallet/create`)"}},"required":["receiptId","userAddress"]},"example":{"receiptId":"receipt_abc123","userAddress":"0x1234567890abcdef1234567890abcdef12345678"}}}},"responses":{"200":{"description":"Signing data returned","content":{"application/json":{"schema":{"type":"object","properties":{"contractAddress":{"type":"string","description":"Token contract address to include in signature"},"chainId":{"type":"integer","description":"Chain ID (e.g. 8453 for Base mainnet)"},"expiryBlock":{"type":"string","description":"Block number after which the mint authorization expires"},"nonce":{"type":"string","description":"User's current nonce on the contract"}},"required":["contractAddress","chainId","expiryBlock","nonce"]},"example":{"contractAddress":"0x1234567890abcdef1234567890abcdef12345678","chainId":8453,"expiryBlock":"12345678","nonce":"0"}}}},"400":{"description":"Validation error (wallet mismatch, invalid receipt ID, wallet error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"userAddress does not match wallet on record","code":"ADDRESS_MISMATCH"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Receipt has already been minted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"Receipt has already been minted","code":"RECEIPT_ALREADY_MINTED"}}}},"500":{"description":"Server configuration error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/mint/execute":{"post":{"tags":["Minting"],"summary":"Execute Mint with Client Signature","description":"**As a client application, I need to mint tokens to my user's wallet after they complete a qualifying action (e.g. a purchase, a reward event, a loyalty milestone).**\n\nThis is **step 2 of the two-step mint flow**. Submits the signed mint authorization for on-chain execution. Returns `202 Accepted` with a `transactionId` to poll for completion.\n\n**Prerequisites:** You must first call `POST /mint/prepare` to get the signing data, then sign the message with your registered signer key.\n\n**Amount in wei:** Unlike other endpoints, the `amount` here must be in **wei** (e.g. `\"1000000000000000000\"` for 1 token with 18 decimals). This matches the exact value used in the signature hash.\n\n**Receipt deduplication:** Each `receiptId` can only be minted once. Attempting to re-mint the same receipt returns `409 Conflict`.","operationId":"executeMint","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XUserId"},{"$ref":"#/components/parameters/XIdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"receiptId":{"type":"string","description":"Same receipt ID used in `/mint/prepare`"},"amount":{"type":"string","description":"Amount in wei (e.g. \"1000000000000000000\" for 1 token). Also accepts a number."},"signature":{"type":"string","description":"EIP-712 signature (65 bytes hex with 0x prefix, e.g. \"0xabc123...\")"},"expiryBlock":{"type":"string","description":"Expiry block from `/mint/prepare` response. Also accepts a number."},"nonce":{"type":"string","description":"Nonce from `/mint/prepare` response. Also accepts a number."}},"required":["receiptId","amount","signature","expiryBlock","nonce"]},"example":{"receiptId":"receipt_abc123","amount":"1000000000000000000","signature":"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12345678901c","expiryBlock":"12345678","nonce":"0"}}}},"responses":{"202":{"description":"Mint accepted -- poll status via `GET /transactions/{transactionId}`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionAccepted"},"example":{"transactionId":"tx_mint_abc123","status":"ACCEPTED"}}}},"400":{"description":"Validation error (invalid signature format, invalid receipt ID, wallet error, insufficient balance)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Receipt already minted or idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/claims/execute":{"post":{"tags":["Claims"],"summary":"Lock Tokens to Claim Rewards","description":"**As a client application, I need to let my user exchange their tokens for real-world rewards -- for example, converting loyalty tokens into vouchers, discounts, or merchandise.**\n\nLocks the specified amount of tokens in the claim contract on-chain. Once locked, your system processes the reward off-chain. Returns `202 Accepted` with a `transactionId`.\n\n**Amount format:** Pass the amount in whole token units (e.g. `\"100\"` for 100 tokens) or decimal strings (e.g. `\"100.5\"`). The API handles wei conversion internally.\n\n**Claim ID:** The `claimId` maps to your internal reward/order identifier. It's hashed to `bytes32` using `keccak256` for on-chain storage. Optionally pass `claimIdBytes32` if you pre-compute it.","operationId":"executeClaim","security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XUserId"},{"$ref":"#/components/parameters/XIdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"claimId":{"type":"string","description":"Unique claim identifier from your system (1-256 chars, alphanumeric + dashes/underscores)"},"claimIdBytes32":{"type":"string","description":"Optional pre-computed bytes32 hash of the claim ID (0x-prefixed, 66 chars). If provided, must match `keccak256(claimId)`."},"amount":{"type":"string","description":"Amount of tokens to lock. Accepts whole units (e.g. \"100\"), decimal strings (e.g. \"100.5\"), or numeric values."}},"required":["claimId","amount"]},"example":{"claimId":"claim_xyz789","amount":"100"}}}},"responses":{"202":{"description":"Claim accepted -- poll status via `GET /transactions/{transactionId}`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionAccepted"},"example":{"transactionId":"tx_claim_xyz789","status":"ACCEPTED"}}}},"400":{"description":"Validation error (insufficient balance, invalid claim ID, claimIdBytes32 mismatch, wallet error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"Insufficient balance","code":"INSUFFICIENT_BALANCE"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/transactions/{transactionId}":{"get":{"tags":["Transactions"],"summary":"Poll Transaction Status","description":"**As a client application, I need to know when my user's transaction is confirmed on-chain so I can update my UI and trigger downstream business logic.**\n\nReturns the current status of an async transaction job. Every mutating operation (`mint/execute`, `wallet/transfer`, `claims/execute`) returns a `transactionId` -- use this endpoint to check its progress.\n\n**Recommended approach -- webhooks first, polling as safety net:**\n1. Configure a webhook URL in your client profile to receive push notifications when transactions reach terminal status\n2. If no webhook delivery arrives within **15 seconds**, fall back to polling this endpoint as a safety net\n3. For `ACCEPTED`/`SUBMITTING` poll every 1s, for `SUBMITTED` every 2-3s. Stop on any terminal status (`CONFIRMED`, `FAILED`, `REVERTED`, `EXPIRED`)\n\nThe response includes a `Retry-After` header for non-terminal statuses. Terminal statuses return `Cache-Control: public, max-age=300, immutable`.\n\n**Security:** Clients can only view their own transactions. Attempting to access another client's transaction returns `404`.","operationId":"getTransactionStatus","security":[{"BearerAuth":[]}],"parameters":[{"name":"transactionId","in":"path","required":true,"description":"Transaction ID returned from a mutating endpoint (must start with `tx_`, max 32 chars)","schema":{"type":"string"},"example":"tx_abc123def456"}],"responses":{"200":{"description":"Transaction status returned","headers":{"Retry-After":{"description":"Suggested polling interval in seconds (only present for non-terminal statuses: \"1\" for ACCEPTED/SUBMITTING, \"3\" for SUBMITTED)","schema":{"type":"string"}},"Cache-Control":{"description":"`public, max-age=300, immutable` for terminal statuses, `no-store` for active statuses","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionStatus"},"example":{"status":"CONFIRMED"}}}},"400":{"description":"Invalid transaction ID format","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}}},"example":{"error":"INVALID_TRANSACTION_ID","message":"Transaction ID must be a valid tx_ prefixed identifier"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Transaction not found (or belongs to a different client)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}}},"example":{"error":"TRANSACTION_NOT_FOUND","message":"Transaction tx_abc123def456 not found"}}}}}}},"/api/v1/quota/check":{"get":{"tags":["Quota"],"summary":"Check Transaction Quota","description":"**As a client application, I need to check if my user can still perform operations today so I can show them their remaining quota or disable actions proactively.**\n\nReturns quota status for mint, transfer, and claim operations, broken down by daily, weekly, and monthly limits. Each operation type shows `allowed` (boolean) and `remaining` counts.\n\n**When to use:** Call this before showing action buttons in your UI. For example, if `mint.allowed` is `false`, grey out the \"Earn Tokens\" button and show \"Daily limit reached\".\n\n**Rate limits vs quotas:** This checks *transaction quotas* (e.g. \"10 mints per day per user\"), not API rate limits. Rate limits are enforced separately and return `429` on the actual API call.","operationId":"checkQuota","security":[{"BearerAuth":[]}],"parameters":[{"name":"X-User-Id","in":"header","required":true,"description":"Your user's identifier. Quota is checked for this specific user.","schema":{"type":"string"}}],"responses":{"200":{"description":"Quota status returned","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"mint":{"$ref":"#/components/schemas/QuotaBreakdown"},"transfer":{"$ref":"#/components/schemas/QuotaBreakdown"},"claim":{"$ref":"#/components/schemas/QuotaBreakdown"}}},"example":{"success":true,"mint":{"allowed":true,"remaining":{"daily":10,"weekly":50,"monthly":200}},"transfer":{"allowed":true,"remaining":{"daily":20,"weekly":100,"monthly":500}},"claim":{"allowed":true,"remaining":{"daily":5,"weekly":25,"monthly":100}}}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Transaction policy is disabled for this client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":"Transaction policy is disabled for this client","code":"POLICY_DISABLED"}}}},"404":{"description":"Transaction policy not configured for this client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":"Transaction policy not configured for this client","code":"POLICY_NOT_FOUND"}}}}}}},"/api/v1/webhooks/deliveries":{"get":{"tags":["Webhooks"],"summary":"List Webhook Deliveries","description":"**As a client application, I need to check if my webhook endpoint received all events correctly, especially when debugging missing callbacks or delivery failures.**\n\nReturns your webhook delivery history, newest first. Each record includes the event type, delivery status, HTTP response code from your endpoint, response time, and retry count.\n\n**Debugging flow:** If a user reports a stuck transaction but your webhook didn't fire, check this endpoint to see if the delivery failed (wrong URL, your endpoint returned 500, timeout, etc.).\n\n**Pagination:** Uses cursor-based pagination. Pass the `cursor` value from a previous response to get the next page. The `hasMore` flag tells you if more pages exist.","operationId":"listWebhookDeliveries","security":[{"BearerAuth":[]}],"parameters":[{"name":"status","in":"query","required":false,"description":"Filter by delivery status","schema":{"type":"string","enum":["PENDING","DELIVERING","DELIVERED","FAILED"]}},{"name":"eventType","in":"query","required":false,"description":"Filter by event type (e.g. `transaction.confirmed`, `transaction.failed`)","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Max results per page (1-100, default 20)","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","required":false,"description":"Pagination cursor -- pass the `cursor` value from a previous response to get the next page","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook deliveries list","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"cursor":{"type":"string","nullable":true,"description":"Pass this as `cursor` query param to fetch the next page. Null when there are no more pages."},"hasMore":{"type":"boolean","description":"Whether more pages are available"}}}}},"example":{"success":true,"data":{"deliveries":[{"id":"del_abc123","eventType":"transaction.confirmed","eventId":"evt_xyz789","status":"DELIVERED","attemptCount":1,"maxAttempts":5,"lastAttemptAt":"2026-01-15T10:30:00.000Z","lastResponseStatus":200,"lastResponseTimeMs":145,"errorMessage":null,"deliveredAt":"2026-01-15T10:30:00.000Z","createdAt":"2026-01-15T10:29:59.000Z"}],"cursor":null,"hasMore":false}}}}},"400":{"description":"Invalid filter parameters (bad status or event type)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT access token obtained from `POST /api/v1/auth/token`. Pass as `Authorization: Bearer <token>`. Tokens are scoped to specific operations (wallet, mint, transfer, claim)."}},"parameters":{"XUserId":{"name":"X-User-Id","in":"header","required":true,"description":"Your application's unique identifier for the end user. Used to scope wallets, quotas, and audit trails.","schema":{"type":"string"}},"XIdempotencyKey":{"name":"X-Idempotency-Key","in":"header","required":false,"description":"Optional but recommended idempotency key. If a request with the same key was already processed, the original response is returned without re-executing the operation.","schema":{"type":"string"}}},"schemas":{"ErrorResponse":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message"},"code":{"type":"string","description":"Machine-readable error code"}},"required":["error"]},"TransactionAccepted":{"type":"object","description":"Returned by all async mutating endpoints (`mint/execute`, `wallet/transfer`, `claims/execute`) on success with HTTP 202.","properties":{"transactionId":{"type":"string","description":"Unique job ID. Poll status via `GET /transactions/{transactionId}`.","example":"tx_abc123def456"},"status":{"type":"string","enum":["ACCEPTED"],"description":"Initial status. Will transition through SUBMITTING -> SUBMITTED -> CONFIRMED (or FAILED/REVERTED/EXPIRED)."}},"required":["transactionId","status"]},"TransactionStatus":{"type":"object","description":"Returned by `GET /transactions/{transactionId}`.","properties":{"status":{"type":"string","enum":["ACCEPTED","SUBMITTING","SUBMITTED","CONFIRMED","FAILED","REVERTED","EXPIRED"],"description":"Current job status. Terminal: CONFIRMED, FAILED, REVERTED, EXPIRED. Non-terminal: ACCEPTED (queued), SUBMITTING (building UserOp), SUBMITTED (on-chain, awaiting confirmation)."}},"required":["status"]},"QuotaBreakdown":{"type":"object","description":"Quota status for a single operation type (mint, transfer, or claim).","properties":{"allowed":{"type":"boolean","description":"Whether the user can perform this operation"},"remaining":{"type":"object","description":"Remaining transaction counts per period","properties":{"daily":{"type":"integer"},"weekly":{"type":"integer"},"monthly":{"type":"integer"}}}}},"WebhookDelivery":{"type":"object","description":"A single webhook delivery attempt record.","properties":{"id":{"type":"string","description":"Delivery ID"},"eventType":{"type":"string","description":"Event type (e.g. `transaction.confirmed`)"},"eventId":{"type":"string","description":"Source event ID"},"status":{"type":"string","enum":["PENDING","DELIVERING","DELIVERED","FAILED"]},"attemptCount":{"type":"integer","description":"Number of delivery attempts made"},"maxAttempts":{"type":"integer","description":"Maximum delivery attempts allowed"},"lastAttemptAt":{"type":"string","format":"date-time","nullable":true},"lastResponseStatus":{"type":"integer","nullable":true,"description":"HTTP status code from your webhook endpoint"},"lastResponseTimeMs":{"type":"integer","nullable":true,"description":"Response time in milliseconds"},"errorMessage":{"type":"string","nullable":true,"description":"Error message if delivery failed"},"deliveredAt":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp of successful delivery"},"createdAt":{"type":"string","format":"date-time","description":"When the delivery was created"}}}}}}