Skip to content

Payments API Reference ​

All payment endpoints are mounted at /v1/payments. Payer-facing endpoints are mounted at /v1/pay. Authentication via X-API-Key. Decimal amounts are returned as JSON strings.

Endpoints summary ​

MethodPathAuthDescription
POST/v1/paymentsRequired (Basic KYC)Create a payment request
GET/v1/paymentsRequiredList payments (paginated)
GET/v1/payments/{payment_id}RequiredGet payment detail
GET/v1/payments/tokensRequiredList active tokens available for payments
GET/v1/payments/settingsRequiredGet payment settings (fee payer, enabled tokens)
PUT/v1/payments/settingsRequiredUpdate payment settings
GET/v1/pay/{payment_id}NonePublic payment page data
GET/v1/pay/{payment_id}/statusNonePublic status poll (payer-facing)
POST/v1/pay/{payment_id}/confirmNonePayer confirms token selection and locks rate
POST/v1/pay/{payment_id}/confirm-cryptoNoneAlias of /confirm (used by the hosted payment page)
GET/v1/pay/{payment_id}/rateNoneLive exchange rate for a token
GET/v1/pay/{payment_id}/fee-estimatesNoneBest-effort gas fee estimates per token
POST/v1/pay/{payment_id}/supportNonePayer submits a support ticket (3/hour rate limit)

POST /v1/payments ​

Create a new payment request. Requires Basic KYC.

Request body:

FieldTypeRequiredConstraints
amount_usddecimalYesGreater than 0; subject to configurable global and per-token min/max limits
descriptionstringNoMax 150 characters
redirect_urlstringNoMust start with https://, max 2048 characters
allowed_token_idsarray of UUIDsNoRestrict payer's token choices for this payment

Response (201 Created):

json
{
  "success": true,
  "data": {
    "id": "9f1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
    "status": "awaiting_selection",
    "amount_usd": "150.00",
    "description": "Order #1001",
    "redirect_url": "https://yourstore.com/thank-you",
    "pay_url": "https://app.tokencashflow.com/pay/9f1b2c3d-...",
    "fee_payer": "user",
    "token": null,
    "locked_rate_usd": null,
    "locked_at": null,
    "required_crypto_amount": null,
    "total_received_crypto": "0",
    "total_received_usd": null,
    "service_fee_usd": null,
    "conversion_fee_usd": null,
    "net_settlement_usd": null,
    "wallet_address": "0x...",
    "transactions": [],
    "selection_expires_at": null,
    "awaiting_payment_at": null,
    "partially_paid_at": null,
    "confirming_at": null,
    "confirmed_at": null,
    "completed_at": null,
    "created_at": "2026-05-16T12:00:00Z"
  }
}

Fields that are not yet applicable (fees, locked rate, timestamps) are null until the payment progresses through its lifecycle.

Error codes:

CodeCondition
KYC_REQUIREDUser's KYC level is below Basic
VALIDATION_ERRORInvalid field values (non-positive amount, description too long, non-HTTPS redirect, no active tokens available, etc.)

GET /v1/payments ​

List the authenticated user's payments, newest first.

Query parameters:

ParameterTypeDescription
statusstringFilter by payment status (lowercase, e.g. completed)
date_fromISO 8601 datePayments created on or after (UTC)
date_toISO 8601 datePayments created on or before (UTC)
pageintegerDefault: 1, min: 1
per_pageintegerDefault: 20, min: 1, max: 100

Response (200 OK):

json
{
  "success": true,
  "data": {
    "total": 87,
    "page": 1,
    "page_size": 20,
    "total_pages": 5,
    "payments": [
      {
        "id": "9f1b2c3d-...",
        "amount_usd": "150.00",
        "description": "Order #1001",
        "status": "completed",
        "token": { "id": "...", "symbol": "ETH", "name": "Ethereum" },
        "total_received_crypto": "0.075000000000000000",
        "net_settlement_usd": "147.75",
        "selection_expires_at": null,
        "completed_at": "2026-05-16T12:05:00Z",
        "created_at": "2026-05-16T12:00:00Z"
      }
    ]
  }
}

GET /v1/payments/ ​

Get full detail for a single payment.

Response (200 OK):

json
{
  "success": true,
  "data": {
    "id": "9f1b2c3d-...",
    "status": "completed",
    "amount_usd": "150.00",
    "description": "Order #1001",
    "redirect_url": "https://yourstore.com/thank-you",
    "fee_payer": "user",
    "token": { "id": "...", "symbol": "ETH", "name": "Ethereum", "logo_url": "https://...", "decimals": 18, "is_stablecoin": false },
    "locked_rate_usd": "2000.00",
    "locked_at": "2026-05-16T12:01:00Z",
    "required_crypto_amount": "0.075000000000000000",
    "total_received_crypto": "0.075000000000000000",
    "total_received_usd": "150.00",
    "service_fee_usd": "0.75",
    "conversion_fee_usd": "1.50",
    "net_settlement_usd": "147.75",
    "wallet_address": "0xpaymentwallet...",
    "selection_expires_at": null,
    "awaiting_payment_at": "2026-05-16T12:01:00Z",
    "partially_paid_at": null,
    "confirming_at": "2026-05-16T12:02:00Z",
    "confirmed_at": "2026-05-16T12:04:00Z",
    "completed_at": "2026-05-16T12:05:00Z",
    "created_at": "2026-05-16T12:00:00Z",
    "transactions": [
      {
        "tx_hash": "0xabc123...",
        "from_address": "0xpayer...",
        "amount_crypto": "0.075",
        "block_height": 19000000,
        "confirmations": 15,
        "is_confirmed": true,
        "detected_at": "2026-05-16T12:02:00Z",
        "confirmed_at": "2026-05-16T12:04:00Z"
      }
    ]
  }
}

Error codes:

CodeCondition
NOT_FOUNDpayment_id does not exist or does not belong to the authenticated user
VALIDATION_ERRORpayment_id is not a valid UUID

GET /v1/pay/ ​

No authentication required. Returns payer-safe payment data used by the hosted payment page: the allowed token options (with live rates, estimated amounts, and wallet addresses), the selected token (if any), the locked rate, required and received amounts, and the redirect URL.

GET /v1/pay/{payment_id}/status ​

No authentication required. Lightweight polling endpoint (the payment page polls it every 5 seconds).

Returns only safe public fields.

Response (200 OK):

json
{
  "success": true,
  "data": {
    "id": "9f1b2c3d-...",
    "status": "awaiting_payment",
    "total_received_crypto": "0.000000000000000000",
    "required_crypto_amount": "0.075000000000000000",
    "selection_expires_at": "2026-05-16T12:31:00Z",
    "confirmations_received": null,
    "confirmations_required": 12,
    "redirect_url": "https://yourstore.com/thank-you"
  }
}

selection_expires_at is null until the payer confirms a token selection. Once set, it controls the countdown timer on the payment page and is reset on each reselection.

GET /v1/payments/tokens ​

List active tokens available for payment creation.

Response (200 OK):

json
{
  "success": true,
  "data": {
    "tokens": [
      {
        "id": "a1b2c3d4-...",
        "symbol": "ETH",
        "name": "Ethereum",
        "logo_url": "https://...",
        "decimals": 18,
        "token_type": "native",
        "is_stablecoin": false,
        "min_payment_usd": null,
        "max_payment_usd": null,
        "is_active": true,
        "chain": {
          "id": "...",
          "name": "Ethereum",
          "short_name": "eth",
          "chain_type": "evm",
          "is_active": true
        }
      }
    ]
  }
}

min_payment_usd / max_payment_usd optionally constrain payments made with that specific token.

GET / PUT /v1/payments/settings ​

Read or update the merchant's default payment settings.

PUT request body:

json
{
  "fee_payer": "user",
  "enabled_token_ids": ["a1b2c3d4-..."]
}
FieldTypeDescription
fee_payer"user" | "payer"Who absorbs fees. user = merchant (default).
enabled_token_idsarray of UUIDsTokens offered to payers by default. An empty array means "all active tokens". Omitted/null fields preserve the existing value.

Response (200 OK):

json
{
  "success": true,
  "data": {
    "enabled_token_ids": ["a1b2c3d4-..."],
    "fee_payer": "user"
  }
}

POST /v1/pay/{payment_id}/confirm ​

No authentication required. Payer confirms their token selection. Locks the rate and starts the selection_expires_at countdown. (/confirm-crypto is an alias.)

Can also be called from expired_selection status to reselect a token (new rate, new window). Reselection after a partial payment must use the same token — a different token is rejected in that case.

Request body:

FieldTypeRequiredDescription
token_idUUIDYesID of the selected token

Response (200 OK):

json
{
  "success": true,
  "data": {
    "status": "awaiting_payment",
    "token": { "id": "a1b2c3d4-...", "symbol": "ETH", "name": "Ethereum" },
    "chain": { "id": "...", "name": "Ethereum", "short_name": "eth", "chain_type": "evm" },
    "locked_rate_usd": "2000.00",
    "required_crypto_amount": "0.075000000000000000",
    "wallet_address": "0xpaymentwallet...",
    "selection_expires_at": "2026-05-16T12:31:00Z"
  }
}

Error codes:

CodeCondition
NOT_FOUNDPayment or token does not exist / token inactive
CONFLICTPayment not in awaiting_selection/expired_selection; unconfirmed transaction in flight; or a different token chosen after partial payment
VALIDATION_ERRORToken not in the allowed set for this payment; per-token min/max amount exceeded
RATE_STALENo fresh rate available (older than 30 s); retry shortly

GET /v1/pay/{payment_id}/rate ​

No authentication required. Query parameter: token_id (UUID).

Response (200 OK):

json
{
  "success": true,
  "data": {
    "token_id": "a1b2c3d4-...",
    "rate_usd": "2000.00",
    "estimated_amount": "0.075000000000000001",
    "rate_age_seconds": 4,
    "is_stale": false
  }
}

GET /v1/pay/{payment_id}/fee-estimates ​

No authentication required. Best-effort network fee estimate per allowed token. Individual chain failures return null values — the endpoint never fails entirely.

Response (200 OK):

json
{
  "success": true,
  "data": {
    "payment_id": "9f1b2c3d-...",
    "estimates": [
      {
        "token_id": "a1b2c3d4-...",
        "chain_short_name": "eth",
        "estimated_gas_fee_native": "0.000420",
        "native_token_symbol": "ETH",
        "estimated_gas_fee_usd": "0.840000"
      }
    ],
    "fetched_at": "2026-05-16T12:00:00Z"
  }
}

Payment status reference ​

StatusDescription
awaiting_selectionCreated; payer has not yet selected a token
awaiting_paymentToken selected; rate locked; waiting for on-chain transaction
partially_paidFunds received but less than required; waiting for more
confirmingRequired amount received; waiting for block confirmations
confirmedBlock confirmations complete; sweep in progress
completedPayment fully settled; funds credited to merchant
expiredTerminal expired state (admin-managed)
expired_selectionChain selection window expired; payer must reselect to continue
expired_partialSettled after partial funding; merchant credited proportionally
blacklistedTransaction detected from a blacklisted address
sweep_failedSweep to platform wallet failed after maximum retries
failedSystem error requiring manual investigation

TokenCashFlow Documentation