CoinCircuit Logo

CoinCircuit Docs

Connect MCP

Open Search

Keyboard Shortcut:

CTRL

  • Introduction
  • Sandbox Environment
  • MCP Integration
  • Official SDKs
  • Authentication
  • Webhooks
  • Error Handling
  • Rate Limiting
  • Pagination
  • Invoices

    Close Group

  • Create invoiceHTTP Method: POST
  • List invoicesHTTP Method: GET
  • Retrieve invoice by referenceHTTP Method: GET
  • PaymentsOpen Group
  • x402Open Group
  • TransactionsOpen Group
  • DepositsOpen Group
  • PayoutsOpen Group
  • Payout RecipientsOpen Group
  • BalanceOpen Group
  • SwapOpen Group
  • SettlementsOpen Group
  • CustomersOpen Group
  • RefundsOpen Group
  • Payment PagesOpen Group
  • MiscellaneousOpen Group
  • ModelsOpen Group
  • Generate MCP

    Powered by Scalar

    Open Menu

    Developer Tools

    Configure

    Share

    Deploy

    v1.0

    OAS 3.0.0

    CoinCircuit API

    Download OpenAPI Document

    json

    yaml

    Introduction

    Welcome to CoinCircuit, crypto payment infrastructure for online payments, in-store payments, payouts, and refunds through a single API.

    New here? Start with the Quick Start for a guided walkthrough.

    Need help? Use the AI assistant in the bottom-right corner for guidance while navigating the documentation.

    Sandbox Environment

    Use sandbox to test your integration safely before going live.

  • Production Base URL: https://api.coincircuit.io
  • Sandbox Base URL: https://sandbox-api.coincircuit.io
  • Use sandbox API keys (test keys) against the sandbox base URL, and live keys against production.

    MCP Integration

    CoinCircuit provides a Model Context Protocol (MCP) server for seamless integration with AI-powered IDEs like Cursor, Windsurf, Claude Desktop, and more.

    MCP URL: https://mcp.coincircuit.io

    Generate an MCP access key from your CoinCircuit dashboard under Developer > MCP Access Keys, then add it to your config:

    {
      "mcpServers": {
        "coincircuit": {
          "url": "https://mcp.coincircuit.io",
          "headers": {
            "x-mcp-key": "YOUR_MCP_KEY"
          }
        }
      }
    }

    The MCP server works without authentication for documentation browsing and API schema discovery. Authentication is only required to execute tools (create payments, list transactions, etc.).

    Official SDKs

    Install the CoinCircuit SDK for your language.

    Node.js / TypeScript

    npm install coincircuit

    Python

    pip install coincircuit

    Checkout SDK

    npm install @coincircuit/checkout

    npm | PyPI | Checkout SDK

    Authentication

    Authenticate requests by including your API key in the x-api-key header.

    x-api-key: your_api_key_here

    Webhooks

    CoinCircuit sends webhook events as payments progress. See the Webhooks tab in this page for detailed integration guidance.

    Error Handling

    The API uses standard HTTP status codes to indicate the success or failure of a request.

    HTTP Status

    Description

    400

    Invalid input or malformed request

    401

    Invalid or missing authentication

    404

    Resource not found

    409

    Resource conflict

    503

    Service unavailable

    Basic Error Example:

    {
        "success": false,
        "timestamp": "2026-01-23T23:33:55.685Z",
        "path": "/api/v1/payments",
        "method": "POST",
        "error": "Unauthorized",
        "message": "Unauthorized"
    }

    Rate Limiting

    The API is rate-limited to 20 requests per second. Exceeding this limit will result in a 429 Too Many Requests response.

    Pagination

    List endpoints support pagination using page (default 1) and size (default 10, max 100) query parameters.

    Response Metadata Example:

    "meta": {
      "page": 1,
      "size": 10,
      "total": 42,
      "totalPages": 5
    }

    Server:

    https://api.coincircuit.io

    Production Server

    Authentication

    Select

    No authentication selected

    Client Libraries

    Shell

    Ruby

    Node.js

    PHP

    Python

    More

    Select from all clients

    Node.js Axios

    Invoices

    Copy link

    Create and manage invoices

  • post/api/v1/invoices
  • get/api/v1/invoices
  • get/api/v1/invoices/reference/{reference}
  • Overview

    Ctrl K

    Webhooks

    Overview

    How CoinCircuit delivers webhook events, with headers, retries, and idempotency.

    CoinCircuit sends webhook events to your endpoint as payments, payouts, refunds, deposits, and other resources change state. Each event is a POST request with a JSON body.

    Standard headers

    Every delivery carries these headers.

    Header

    X-CoinCircuit-Event

    The event type, e.g. transaction.confirmed

    X-CoinCircuit-Delivery-Id

    Unique id for this delivery

    X-CoinCircuit-Signature

    HMAC-SHA256 signature of the payload

    X-CoinCircuit-Timestamp

    Unix timestamp of the delivery

    Event identification

    Every event includes a unique X-CoinCircuit-Delivery-Id. Store the ids you have processed and skip repeats so the same event is never handled twice.

    Automatic retries

    Failed deliveries (any non-200 response) are retried with exponential backoff, roughly at 0m, 1m, 10m, then 1h. Return a 200 quickly to acknowledge receipt.

    Responding

    Acknowledge with a 2xx status as soon as you have stored the event, then do any heavy work asynchronously so the delivery does not time out.

    Introduction

    Getting Started

    CoinCircuit is crypto payment infrastructure. Accept payments online, in person, and from AI agents. Bill with invoices, send payouts, and issue refunds.

    What you can build

  • Online payments. Create a payment and redirect to a hosted checkout, or build your own UI on the API.
  • In-store payments. Share a payment page link or QR code, or take payments on a POS terminal at the counter.
  • Invoices. Send itemized invoices your customers pay in crypto.
  • Agentic payments (x402). Let AI agents and automated clients pay for your API per request, settled in stablecoins with gas covered.
  • Payouts. Withdraw your balance to a bank account or a crypto wallet.
  • Refunds. Refund a payment or an invoice, in full or in part.
  • Deposit accounts. Give each customer a reusable address to receive funds into their balance.
  • Webhooks. Get a signed event the moment a payment, payout, or refund changes state.
  • Base URLs

  • Production: https://api.coincircuit.io
  • Sandbox: https://sandbox-api.coincircuit.io
  • Start here

    Follow the Quick Start to take your first payment in a few minutes. To test without real funds first, set up the Sandbox.

    Releases

  • v2.5.0August 2026
  • v2.4.0July 2026
  • v2.3.0June 2026
  • v2.2.0May 2026
  • v2.1.0March 2026
  • v2.0.0February 2026
  • Changelog

    API changes, new features, migration guides, and deprecation notices, newest first.

    v2.5.0

    August 2026

    Settlement Currency Control

    Choose the currency a payment or a deposit account is settled into, independently of what you priced in and what your customer pays. Payment session responses return the settlement breakdown from creation.

    Settlement Currency

    New

    Updated

    Name the currency you want funds settled into on a payment or a deposit account, instead of taking the account currency from your settings.

    Set the settlement currency on a payment

    POST /payments accepts a settlementCurrency. The customer still pays in the currency you priced in, and the payment is settled into the currency you named. Leave it out and the payment is settled into the account currency from your settings.

    example.json

    POST /payments
    {
      "amount": "10000.00",
      "currency": "NGN",
      "settlementCurrency": "USDT",
      "customer": { "email": "customer@example.com" }
    }
    Set the settlement currency on a deposit account

    POST /deposits/accounts accepts a settlementCurrency, applied to every deposit into that account. Leave it out and deposits settle on the default behavior. Your parent account is always default.

    POST /deposits/accounts
    {
      "customerId": "123e4567-e89b-12d3-a456-426614174000",
      "settlementCurrency": "USDC"
    }
    settlements is always returned

    The settlements object is returned from the moment a payment session is created, so the settlement currency, conversion rate and fee split are available before the payment settles. net stays null until settlement completes, and is the field to check for whether a payment has settled.

    // Session created, not yet paid
    {
      "settlements": {
        "currency": "USDT",
        "customerPaid": { "amount": null },
        "gross": { "amount": "100.00", "conversionRate": "1.0" },
        "fees": null,
        "net": null
      }
    }

    v2.4.0

    July 2026

    Cross-Border Payouts, Per-Request Webhooks & Unified Recipients

    Send payouts to Kenya and Ghana from your stablecoin balance with locked quotes, route webhooks per request by passing a webhookUrl, and manage every payout recipient through one unified endpoint.

    Cross-Border Payouts

    Pay recipients in their local currency, funded from your USDT or USDC balance. Quote the price, lock it, then send. Kenya (KES) and Ghana (GHS) are live.

    POST /payouts/cross-border/quote

    Price a cross-border payout before you send it. The response is the funding cost in your stablecoin balance, the FX conversion, the flat fee, and the total, with a quote id you lock the price with. Quotes expire after two minutes.

    POST /payouts/cross-border/quote
    {
      "currency": "KES",
      "sourceCurrency": "USDT",
      "amount": "10000.00"
    }
    
    // Response
    {
      "id": "cbq_123e4567-e89b-12d3-a456-426614174000",
      "conversion": {
        "fromCurrency": "USDT",
        "toCurrency": "KES",
        "sourceAmount": "77.60",
        "targetAmount": "10000.00",
        "rate": "128.87"
      },
      "fee": "1.50",
      "total": "79.10",
      "expiresAt": "2026-07-22T12:02:00.000Z"
    }
    Pay recipients in their local currency

    POST /payouts now settles to Kenya (KES) and Ghana (GHS), funded from your USDT or USDC balance. Set currency to the local currency the recipient receives, sourceCurrency to the stablecoin you fund from, and pass the quote id to pay exactly the price you were quoted.

    POST /payouts
    {
      "currency": "KES",
      "sourceCurrency": "USDT",
      "amount": "10000.00",
      "recipientId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "quoteId": "cbq_123e4567-e89b-12d3-a456-426614174000"
    }
    Mobile money recipients

    Save mobile money recipients for cross-border payouts through the unified recipients endpoint. The kes_mobile_money and ghs_mobile_money types take the provider and account details for the corridor.

    POST /recipients
    {
      "type": "kes_mobile_money",
      "label": "Field agent",
      "details": {
        "phoneNumber": "254712345678",
        "accountName": "Jane Wanjiku",
        "institutionCode": "MPESA"
      }
    }

    Per-Request Webhook URLs

    Override your dashboard webhook URL for a single object by passing a webhookUrl when you create it.

    Set a webhook URL per request

    Pass a webhookUrl when you create a payout, refund, or invoice, or when you execute a swap. Webhooks for that object are delivered to the URL you pass instead of your dashboard webhook URL, so you can route events per environment, per integration, or per customer. Omit it to keep using your dashboard URL.

    POST /payouts
    {
      "currency": "NGN",
      "amount": "10000.00",
      "recipientId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "webhookUrl": "https://your-api.com/webhooks/payouts"
    }
    
    // Payout webhooks for this payout are delivered to webhookUrl
    Honored across payout, refund, swap, invoice and payment events

    A per-request webhook URL applies to payout, refund, swap, and invoice events, and payment events. Your dashboard webhook toggle still governs whether anything is sent.

    example.text

    // A per-request webhookUrl overrides your dashboard URL for:
    payout.*      // set it when you create the payout
    refund.*      // set it when you create the refund
    swap.*        // set it when you execute the swap
    invoice.*     // set it when you create the invoice
    payment.*     // set on the checkout session (invoices cover their payment too)

    Unified Payout Recipients

    Deprecated

    Every payout recipient (crypto address, bank account, or mobile money) is created and managed through one /recipients endpoint. The separate bank account and crypto address endpoints are deprecated.

    Individual recipient endpoints deprecated

    The separate endpoints for bank accounts and crypto addresses are deprecated. Create and manage every payout recipient through the unified /recipients endpoint. The individual endpoints keep working for existing integrations but are no longer documented.

    Before

    // Deprecated: one endpoint per recipient type
    POST /bank-accounts     { accountName, accountNumber, bankCode }
    POST /crypto-addresses  { chain, address, label }

    After

    // Unified: one endpoint, the type field selects the shape
    POST /recipients
    One endpoint for every recipient type

    POST /recipients creates a recipient of any type. The type field selects the shape of details: a crypto address, an NGN bank account, or a mobile money account for cross-border payouts. List, fetch, and delete all recipients through the same resource.

    // Crypto address
    POST /recipients
    {
      "type": "crypto_address",
      "label": "Ops wallet",
      "details": { "chain": "tron", "address": "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC" }
    }
    
    // NGN bank account
    POST /recipients
    {
      "type": "ngn_bank_account",
      "label": "Payroll",
      "details": { "accountNumber": "0123456789", "bankCode": "058" }
    }

    v2.3.0

    June 2026

    Deposit Accounts, Balance Ledger & Webhooks

    Deposit accounts you can issue per customer, a balance API that returns all your currencies in one call, a balance transactions ledger to inspect every entry that moved your balance, and new deposit and swap webhooks.

    Deposit Accounts

    Issue a deposit account per customer (or a parent account for your own balance) and provision its pay-in identities: static crypto deposit addresses and NGN virtual accounts. A deposit settles into the account it was issued for.

    Issue a deposit account per customer

    Create a deposit account for each of your customers, or a single parent account for your own balance. A deposit account is the container that owns its pay-in identities (static crypto addresses and NGN virtual accounts) and is what a deposit settles into. Pass an existing customerId; omit it to create your parent account.

    POST /deposits/accounts
    { "customerId": "123e4567-e89b-12d3-a456-426614174000" }
    
    // Omit "customerId" to create the parent (own-balance) account
    Issue a pay-in identity into an account

    Add a pay-in identity to a deposit account: a static crypto deposit address or an NGN virtual account. Funds sent to any identity credit the account holder. One endpoint serves both; the type field selects which identity to issue.

    // static crypto deposit address
    POST /deposits/accounts/:id/identities
    { "type": "static_deposit_address", "chain": "ethereum" }
    
    // NGN virtual account
    POST /deposits/accounts/:id/identities
    { "type": "ngn_virtual_account", "bvn": "12345678901" }
    Retrieve and list deposit accounts

    Fetch a single account with its identities, the parent account, or a customer's account, and list all accounts filtered by status.

    GET /deposits/accounts/:id
    GET /deposits/accounts/parent
    GET /deposits/accounts/by-customer/:customerId
    GET /deposits/accounts?status=active&page=1&size=20

    Balance Transactions

    A ledger feed of every entry that moved your balance, with rich source linkage and per-entry before/after balances.

    GET /balance/transactions

    A paginated ledger of every entry that moved your balance: deposits, payments, payouts, swaps, refunds, and fees. Filter by source, status, direction, currency, txHash, and date range.

    GET /balance/transactions?source=payment_session&currency=USDT
    
    {
      "success": true,
      "message": "Balance transactions retrieved successfully",
      "data": [
        {
          "id": "bt_123456789_abcdef",
          "source": "payment_session",
          "sourceId": "123e4567-e89b-12d3-a456-426614174000",
          "sourceReference": "CC-1G3fDjgD93md",
          "direction": "credit",
          "status": "completed",
          "amount": "100.00",
          "currency": "USDT",
          "fee": "0",
          "reference": "...",
          "balanceBefore": { "available": "1000.00", "pending": "0.00", "total": "1000.00" },
          "balanceAfter":  { "available": "1100.00", "pending": "0.00", "total": "1100.00" },
          "createdAt": "2026-06-01T10:30:00.000Z"
        }
      ],
      "meta": { "page": 1, "size": 10, "total": 42, "totalPages": 5 }
    }
    How to locate the source of an entry

    source tells you what created the entry. sourceReference is the reference you pass to that object's API (set for sessions, invoices, and payouts). sourceId is the id of the source object (set for sessions, invoices, payouts, swaps, and deposits). A deposit entry's sourceId is the deposit id, which you can fetch at GET /deposits/:id.

    // session / invoice / payout entry
    { "source": "invoice", "sourceId": "...", "sourceReference": "CC-INV-001" }
    
    // swap entry (no reference)
    { "source": "swap", "sourceId": "...", "sourceReference": null }
    
    // deposit entry (sourceId resolves at GET /deposits/:id)
    { "source": "deposit", "sourceId": "dep_123456789_abcdef", "sourceReference": null }

    Deposit & Swap Webhooks

    deposit webhooks notify you when a deposit is recorded, credited, or fails, and swap.completed / swap.failed webhooks notify you when a swap finishes.

    deposit.processing, deposit.completed and deposit.failed

    Get notified about deposits to your balance. deposit.processing fires when a deposit is detected and is processing. deposit.completed fires when a deposit is completed and credited to your balance. deposit.failed fires when a deposit fails a compliance check and is rejected. The payload is a deposit object with the on-chain detail under crypto, or the bank transfer detail under fiat.

    {
      "event": "deposit.completed",
      "data": {
        "id": "dep_123456789_abcdef",
        "type": "crypto",
        "status": "completed",
        "amount": "100.00",
        "currency": "USDT",
        "fee": "0.50",
        "netAmount": "99.50",
        "depositAccountId": "da_123456789_abcdef",
        "customer": {
          "id": "123e4567-e89b-12d3-a456-426614174000",
          "firstName": "John",
          "lastName": "Doe",
          "email": "customer@example.com"
        },
        "crypto": {
          "txHash": "0x...",
          "chain": "ethereum",
          "asset": "USDT",
          "fromAddress": "0xsender...",
          "toAddress": "0xmerchant...",
          "staticDepositAddress": {
            "chain": "ethereum",
            "address": "0xmerchant...",
            "createdAt": "2026-01-10T08:00:00.000Z"
          }
        },
        "fiat": {
          "reference": "NMB-12345",
          "method": "bank_transfer",
          "payer": {
            "accountName": "John Doe",
            "accountNumber": "0123456789",
            "bankName": "Wema Bank",
            "bankCode": "035"
          },
          "payee": {
            "accountName": "COINCIRCUIT/John Doe",
            "accountNumber": "9876543210",
            "bankName": "Nomba Bank",
            "bankCode": "090645"
          },
          "ngnVirtualAccount": {
            "currency": "NGN",
            "accountNumber": "9876543210",
            "accountName": "COINCIRCUIT/John Doe",
            "bankName": "Nomba Bank",
            "bankCode": "090645",
            "accountReference": "CCVBA-7f3a9c12",
            "createdAt": "2026-01-10T08:00:00.000Z"
          }
        },
        "createdAt": "2026-06-01T10:30:00.000Z",
        "completedAt": "2026-06-01T10:35:00.000Z"
      }
    }
    swap.completed and swap.failed webhooks

    Get notified when a balance swap finishes. swap.completed fires on a successful conversion; swap.failed includes a failureReason. Useful when you kick off swaps and want to react without polling.

    {
      "event": "swap.completed",
      "data": {
        "swap": {
          "id": "f2908051-6326-4708-bc73-1153209e7689",
          "fromCurrency": "USDT",
          "toCurrency": "NGN",
          "sourceAmount": "4.00",
          "targetAmount": "5493.95",
          "rate": "1373.48767500",
          "status": "completed",
          "completedAt": "2026-05-29T20:25:23.680Z",
          "createdAt": "2026-05-29T20:25:23.640Z",
          "quotation": {
            "id": "99f41fc8-47e2-4f34-a1dd-64f52ee4ff0c",
            "fromCurrency": "USDT",
            "toCurrency": "NGN",
            "sourceAmount": "4.00",
            "targetAmount": "5493.95",
            "rate": "1373.487675",
            "executed": true,
            "expiresAt": "2026-05-29T20:25:38.553Z",
            "createdAt": "2026-05-29T20:25:23.556Z"
          }
        }
      }
    }

    Deposits

    Deposits are listed and fetched at /deposits.

    List and fetch deposits at /deposits

    Inbound deposits (crypto and bank transfer) are listed and fetched under a single resource. Each deposit carries a depositAccountId linking it to the deposit account it settled into.

    GET /deposits
    GET /deposits/:id

    v2.2.0

    May 2026

    Swap for On/Off Ramping

    New swap endpoints to convert between USDT and NGN directly from your balance. Supports instant on-ramping (NGN to USDT) and off-ramping (USDT to NGN) with rate locking and atomic execution.

    Swap Endpoints

    A quotation-based swap flow: estimate the conversion, lock the rate for 15 seconds, then execute atomically. Supports USDT/NGN in both directions with per-swap limits of 30,000 USDT or 50,000,000 NGN.

    POST /swap/quotation

    Lock a swap rate for 15 seconds. Returns a quotation with the guaranteed rate, source and target amounts. Execute within the TTL to swap at the quoted price.

    POST /swap/quotation
    {
      "fromCurrency": "USDT",
      "toCurrency": "NGN",
      "amount": "500.00"
    }
    
    // Response
    {
      "id": "quotation-uuid",
      "fromCurrency": "USDT",
      "toCurrency": "NGN",
      "sourceAmount": "500.00",
      "targetAmount": "747500.00",
      "rate": "1495.00000000",
      "executed": false,
      "expiresAt": "2026-05-12T10:00:15.000Z",
      "createdAt": "2026-05-12T10:00:00.000Z"
    }
    POST /swap/execute/:quotationId

    Execute a previously created quotation. Debits the source currency and credits the target currency atomically at the locked rate. Returns the completed swap details.

    POST /swap/execute/quotation-uuid
    
    // Response
    {
      "id": "swap-uuid",
      "fromCurrency": "USDT",
      "toCurrency": "NGN",
      "sourceAmount": "500.00",
      "targetAmount": "747500.00",
      "rate": "1495.00000000",
      "status": "completed",
      "quotation": {
        "id": "quotation-uuid",
        "fromCurrency": "USDT",
        "toCurrency": "NGN",
        "sourceAmount": "500.00",
        "targetAmount": "747500.00",
        "rate": "1495.00000000",
        "executed": true,
        "expiresAt": "2026-05-12T10:00:15.000Z",
        "createdAt": "2026-05-12T10:00:00.000Z"
      },
      "createdAt": "2026-05-12T10:00:00.000Z",
      "completedAt": "2026-05-12T10:00:05.000Z"
    }
    GET /swap/estimate

    Preview swap output without locking a rate. Use this to show users an estimated conversion before they commit. The rate is indicative and may change.

    GET /swap/estimate?fromCurrency=USDT&toCurrency=NGN&amount=500.00
    
    // Response
    {
      "fromCurrency": "USDT",
      "toCurrency": "NGN",
      "sourceAmount": "500.00",
      "targetAmount": "747500.00",
      "rate": "1495.00000000"
    }
    GET /swap/quotation/:quotationId

    Retrieve a quotation by ID. Includes an executed flag indicating whether the quotation has been used. Use expiresAt to determine if the quotation is still valid.

    GET /swap/quotation/quotation-uuid
    
    // Response
    {
      "id": "quotation-uuid",
      "fromCurrency": "USDT",
      "toCurrency": "NGN",
      "sourceAmount": "500.00",
      "targetAmount": "747500.00",
      "rate": "1495.00000000",
      "executed": true,
      "expiresAt": "2026-05-12T10:00:15.000Z",
      "createdAt": "2026-05-12T10:00:00.000Z"
    }

    v2.1.0

    March 2026

    x402 Agent Payments, Base and Arbitrum

    New endpoints for AI agent payments via the x402 protocol, plus Base and Arbitrum added as supported blockchains for all payment sessions.

    Agent Payment Endpoints

    Endpoints for the x402 protocol: build payment requirements, verify a signed payment, and settle it. AI agents pay for API resources with gasless stablecoin transfers, and CoinCircuit handles verification, on-chain submission, and gas.

    POST /x402/requirements

    Build the payment requirements for a resource from a chain, asset, and amount. The response is the ready-to-return 402 body, including the canonical accepts array a standard x402 client reads.

    POST /api/v1/x402/requirements
    {
      "chain": "base",
      "asset": "USDC",
      "amount": "0.10",
      "resource": "https://your-api.com/api/premium-data"
    }
    
    // Response (return this with a 402 status)
    {
      "x402Version": 1,
      "accepts": [
        {
          "scheme": "exact",
          "network": "eip155:8453",
          "maxAmountRequired": "100000",
          "resource": "https://your-api.com/api/premium-data",
          "payTo": "0x9f1c4a2b7De3a01F5b8c2D4e6A7b9C0d1E2f3A4b",
          "maxTimeoutSeconds": 60,
          "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
          "extra": { "name": "USD Coin", "version": "2" }
        }
      ]
    }
    POST /x402/settle

    Settle a signed x402 payment. Send the agent's paymentPayload with the requirements you issued. CoinCircuit verifies the signature, covers gas, submits the transfer on-chain, and returns the transaction hash. Each signed payment settles once; a replay returns 409 Conflict.

    POST /api/v1/x402/settle
    {
      "x402Version": 1,
      "paymentPayload": {
        "x402Version": 1,
        "scheme": "exact",
        "network": "eip155:8453",
        "payload": {
          "signature": "0xb91c5d2e8a4f0c7d3e6b9a1f2c4d5e6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d",
          "authorization": {
            "from": "0x1A2b3C4d5E6f7A8b9C0d1E2f3A4b5C6d7E8f9A0b",
            "to": "0x9f1c4a2b7De3a01F5b8c2D4e6A7b9C0d1E2f3A4b",
            "value": "100000",
            "validAfter": "0",
            "validBefore": "1774055094",
            "nonce": "0x9b2c4d6e8f0a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c"
          }
        }
      },
      "paymentRequirements": {
        "scheme": "exact",
        "network": "eip155:8453",
        "maxAmountRequired": "100000",
        "resource": "https://your-api.com/api/premium-data",
        "payTo": "0x9f1c4a2b7De3a01F5b8c2D4e6A7b9C0d1E2f3A4b",
        "maxTimeoutSeconds": 60,
        "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "extra": { "name": "USD Coin", "version": "2" }
      }
    }
    
    // Response
    {
      "success": true,
      "data": {
        "id": "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5a6b7",
        "txHash": "0x4b7a1f0c9d2e5a6f8b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3",
        "status": "confirmed",
        "amlCheck": "passed",
        "network": "eip155:8453",
        "chain": "base",
        "asset": "USDC",
        "payer": "0x1A2b3C4d5E6f7A8b9C0d1E2f3A4b5C6d7E8f9A0b",
        "payTo": "0x9f1c4a2b7De3a01F5b8c2D4e6A7b9C0d1E2f3A4b",
        "amount": "0.10",
        "fee": "0",
        "blockNumber": 21048765,
        "explorerUrl": "https://basescan.org/tx/0x4b7a1f0c9d2e5a6f8b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3",
        "createdAt": "2026-06-25T21:09:02.000Z"
      }
    }
    POST /x402/verify

    Dry-run a signed payment without touching the blockchain. Takes the same body as settle and runs the requirement and on-chain checks: destination, amount, timestamp, nonce replay, and sender balance. Returns isValid, with an invalidReason when a check fails.

    POST /api/v1/x402/verify
    // Same body as settle: x402Version, paymentPayload, paymentRequirements
    
    // Response
    {
      "success": true,
      "data": {
        "isValid": true,
        "invalidReason": null,
        "payer": "0x1A2b3C4d5E6f7A8b9C0d1E2f3A4b5C6d7E8f9A0b",
        "chain": "base",
        "asset": "USDC",
        "payTo": "0x9f1c4a2b7De3a01F5b8c2D4e6A7b9C0d1E2f3A4b",
        "amount": "0.10"
      }
    }
    End-to-end x402 flow

    CoinCircuit is the x402 facilitator. Your server states what a request costs, the agent signs a gasless payment, and CoinCircuit verifies it, covers gas, settles on-chain, and returns the transaction hash. The flow is stateless.

    Agent  -> GET your protected resource
    Server -> POST /api/v1/x402/requirements (build the terms)
    Server -> Agent: 402 Payment Required (the accepts array)
    Agent  -> signs a gasless payment, retries with the payload
    Server -> POST /api/v1/x402/settle (paymentPayload + requirements)
    CoinCircuit -> verifies, covers gas, settles on-chain, returns txHash
    Server -> delivers the resource

    New Supported Blockchains

    Base (Coinbase L2) and Arbitrum are now fully supported for all payment sessions, settlements, and agent payments.

    Base blockchain support

    Coinbase's Layer 2 blockchain is now available for all payment sessions. USDC on Base settles in under 2 seconds. Base is the default blockchain for EIP-3009 gasless agent payments.

    Arbitrum blockchain support

    Arbitrum One is now available for all payment sessions. Supports USDC and USDT via both EIP-3009 and Permit2 signing schemes with sub-second finality.

    v2.0.0

    February 2026

    Payment API Transition and Payouts Unified

    Major updates to the Payment API and Payouts system, introducing new payload shapes, unified endpoints, and better error handling.

    Payment API Transition

    Updated session response shape, renamed webhook event types (session.* → payment.*), and a restructured amount object with inline settlements. Existing API keys are unaffected. New API keys automatically use the updated format.

    Webhook event types renamed: session.* → payment.*

    Payment session webhook event types have been renamed. Previous keys used session.* prefixes; new keys use payment.* prefixes. The webhook payload shape (data.session.*) is unchanged — only the event type string differs. This applies to all payment session outcome events.

    // Previous event types
    "session.completed"
    "session.expired"
    "session.partial"
    "session.failed"
    // New event types
    "payment.completed"
    "payment.expired"
    "payment.partial"
    "payment.underpaid"
    "payment.failed"
    New API keys use the updated format automatically

    Versioning is tied to your API key. New API keys created in the dashboard automatically use the updated response format and new webhook event types. Existing API keys continue to receive the current responses and event types — no action required to preserve your current integration.

    example.javascript

    // Existing key → receives current events and response shape
    // New key → receives updated events and response shape
    
    // No config needed — version is determined by which key you use
    Redesigned session response shape

    The session API response has been restructured. The top-level status field is replaced by state (open/closed). Amount and currency are flat strings instead of a nested object. The financial breakdown (gross, fees, net) moves to a separate settlements object.

    // New session response
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "reference": "cs_ref_abc123xyz789",
      "state": "closed",
      "type": "payment",
      "amount": "10000.00",
      "currency": "NGN",
      "payment": {
        "status": "completed",
        "asset": "USDT",
        "chain": "tron",
        "amount": "6.84",
        "amountReceived": "6.84",
        "address": "T...",
        "txHash": "0x..."
      },
      "settlements": {
        "currency": "NGN",
        "gross": { "amount": "10000.00", "conversionRate": "1.0" },
        "fees": {
          "processing": { "amount": "100.00", "paidBy": "merchant" },
          "gas": { "amount": "50.00", "paidBy": "merchant" }
        },
        "net": { "amount": "9850.00" }
      },
      "customer": { ... },
      "createdAt": "...",
      "expiresAt": "...",
      "completedAt": "..."
    }
    state replaces status

    The top-level status field is now state — "open" while active and awaiting payment, "closed" once finalized. The payment outcome is available in payment.status.

    // Previous
    { "status": "completed" }  // "pending" | "completed" | "expired" | "failed"
    
    // New
    { "state": "closed" }     // "open" | "closed"
    // payment outcome lives in payment.status
    settlements included in payment API response

    Payment session responses now include a settlements object inline with the settlement currency, gross amount with conversion rate, fee breakdown (processing + gas), and net amount. No need to call the settlements endpoint separately.

    {
      "settlements": {
        "currency": "NGN",
        "gross": { "amount": "10000.00", "conversionRate": "1.0" },
        "fees": {
          "processing": { "amount": "100.00", "paidBy": "merchant" },
          "gas": { "amount": "50.00", "paidBy": "merchant" }
        },
        "net": { "amount": "9850.00" }
      }
    }

    Payouts API Deprecation & Migration

    The legacy payout endpoints (/payout/fiat, /payout/crypto) are deprecated and will be removed on March 30, 2026. The new /payouts endpoint unifies fiat and crypto payouts into a single request.

    Payout API deprecated — migrate to /payouts by March 30

    The existing /payout/fiat and /payout/crypto endpoints are deprecated and will be removed on March 30, 2026. Migrate to the new unified POST /payouts endpoint. Separate fee endpoints (/payout/fiat/fee and /payout/crypto/fee) are replaced by GET /payouts/fees.

    POST /payout/fiat     // fiat payouts
    POST /payout/crypto   // crypto payouts
    GET  /payout/fiat/fee  // fiat fee lookup
    GET  /payout/crypto/fee // crypto fee lookup
    POST /payouts          // unified — specify method in body
    GET  /payouts/fees     // unified fee lookup
    Unified payout endpoint

    A single POST /payouts endpoint handles both fiat and crypto payouts. Specify the method in the request body. The bankAccountId and addressId fields are replaced by a single recipientId field.

    // New unified request body
    {
      "method": "fiat",       // "fiat" | "crypto"
      "currency": "NGN",
      "amount": "10000.00",   // amount recipient receives
      "recipientId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "narration": "Payout",
      "reference": "CLIENT-REF-123" // optional idempotency key
    }
    Amount is what the recipient receives

    In the old API, amount was the total debited from your balance (including fees) — the recipient got less. In the new API, amount is exactly what the recipient receives. Fees are charged separately on top. No need to add fees to your amount.

    // Old: amount = total deducted (fee included)
    { "amount": "10050.00" }
    // fee: 50.00
    // recipient gets: 10000.00
    // New: amount = what recipient gets (fee is separate)
    { "amount": "10000.00" }
    // fee: 50.00
    // total deducted: 10050.00
    Redesigned payout response

    The response replaces the nested amount object and separate bankAccount/crypto objects with flat amount/fee/total strings, a unified recipient object, and a conversion object when the payout asset differs from the balance currency.

    // New payout response (USDT balance → USDC payout)
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "method": "crypto",
      "type": "manual",
      "status": "success",
      "amount": "100.00",
      "fee": "1.50",
      "total": "101.50",
      "currency": "USDC",
      "conversion": {
        "from": "USDT",
        "to": "USDC",
        "rate": "0.9998",
        "convertedAmount": "100.00"
      },
      "recipient": {
        "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "type": "crypto_address",
        "details": {
          "chain": "tron",
          "address": "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"
        }
      },
      "txHash": "0x...",
      "reference": "CLIENT-REF-123",
      "narration": "Payout",
      "createdAt": "...",
      "completedAt": "..."
    }

    Migration Guide

    Follow these steps to transition to the new format. Each step is independent — you can adopt them incrementally.

    1

    Generate a new API key

    New API keys automatically use the updated format. Go to the dashboard → API Keys → create a new key. Your existing key keeps working as-is — use the new key in a test environment first before switching production traffic.

    2

    Update webhook event type handlers

    Replace all session.* event type strings with the corresponding payment.* equivalents in your webhook handler.

    example.js

    // Replace each event type:
    "session.completed" → "payment.completed"
    "session.expired"   → "payment.expired"
    "session.partial"   → "payment.partial"
    "session.failed"    → "payment.failed"

    3

    Update payment.partial handler

    payment.partial (previously session.partial) is now strictly mid-flow. It only fires when partial payments are enabled and the session is still open awaiting more funds. Terminal underpayment always uses payment.underpaid.

    // payment.partial → session still open, more payments expected
    if (event === "payment.partial") {
      // data.session.state === "open"
      notifyCustomerOfPartialPayment(data.session.payment.amountReceived);
    }
    
    // payment.underpaid → session closed, insufficient total received
    if (event === "payment.underpaid") {
      // data.session.state === "closed"
      initiateRefundFlow(data.session.payment.amountReceived);
    }

    4

    Update session response parsing

    The session object has been restructured. Update any code that reads the nested amount object — amount and currency are now flat strings, status is replaced by state, and the financial breakdown moves to settlements.

    // Previous — nested amount object
    session.status                  // "pending" | "completed" | "expired" | "failed"
    session.amount.requested.amount // "10000.00"
    session.amount.requested.currency // "NGN"
    session.amount.gross            // { amount, conversionRate }
    session.amount.fees             // { processing, gas }
    session.amount.net              // { amount }
    
    // New — flat amount + settlements object
    session.state                   // "open" | "closed"
    session.amount                  // "10000.00" (plain string)
    session.currency                // "NGN" (plain string)
    session.settlements.gross       // { amount, conversionRate }
    session.settlements.fees        // { processing, gas }
    session.settlements.net         // { amount }

    5

    Migrate payout endpoints

    Replace POST /payout/fiat and POST /payout/crypto with the unified POST /payouts. Add a method field to the request body and use recipientId instead of bankAccountId or addressId. Replace /payout/fiat/fee and /payout/crypto/fee with GET /payouts/fees.

    // Old — separate endpoints
    POST /payout/fiat   { balanceCurrency, amount, bankAccountId, narration }
    POST /payout/crypto { balanceCurrency, amount, addressId }
    
    // New — unified endpoint
    POST /payouts {
      method: "fiat" | "crypto",
      currency: "NGN",          // was balanceCurrency
      amount: "10000.00",
      recipientId: "...",        // was bankAccountId or addressId
      narration: "...",
      reference: "..."           // optional idempotency key (new)
    }

    6

    Update payout amount handling

    In the old API, amount was the total debited from your balance (fees included). In the new API, amount is what the recipient receives — fees are charged separately. Remove any fee addition logic from your integration.

    // Old — amount includes fee, recipient gets less
    const amount = recipientAmount + fee; // you had to add fee
    POST /payout/fiat { amount: "10050.00" }
    // recipient gets 10000.00, fee was 50.00
    
    // New — amount IS what recipient gets
    POST /payouts { amount: "10000.00" }
    // recipient gets 10000.00, fee (50.00) charged separately
    // total deducted from balance: 10050.00

    7

    Update payout response parsing

    The response replaces the nested amount object and separate bankAccount/crypto objects with flat fields and a unified recipient object.

    // Old response fields
    payout.amount.gross         // nested object
    payout.amount.fee
    payout.amount.net
    payout.amount.currency
    payout.bankAccount          // fiat only
    payout.crypto.amount        // crypto only
    payout.crypto.chain
    payout.crypto.address
    
    // New response fields
    payout.amount               // plain string (what recipient gets)
    payout.fee                  // plain string
    payout.total                // plain string (amount + fee)
    payout.currency             // plain string
    payout.recipient.type             // "crypto_address" | "ngn_bank_account"
    payout.recipient.details.chain    // crypto
    payout.recipient.details.address  // crypto
    payout.recipient.details.bankName // fiat
    payout.conversion           // cross-currency (e.g. USDT→USDC) or null