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
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /v1/payments | Required (Basic KYC) | Create a payment request |
GET | /v1/payments | Required | List payments (paginated) |
GET | /v1/payments/{payment_id} | Required | Get payment detail |
GET | /v1/payments/tokens | Required | List active tokens available for payments |
GET | /v1/payments/settings | Required | Get payment settings (fee payer, enabled tokens) |
PUT | /v1/payments/settings | Required | Update payment settings |
GET | /v1/pay/{payment_id} | None | Public payment page data |
GET | /v1/pay/{payment_id}/status | None | Public status poll (payer-facing) |
POST | /v1/pay/{payment_id}/confirm | None | Payer confirms token selection and locks rate |
POST | /v1/pay/{payment_id}/confirm-crypto | None | Alias of /confirm (used by the hosted payment page) |
GET | /v1/pay/{payment_id}/rate | None | Live exchange rate for a token |
GET | /v1/pay/{payment_id}/fee-estimates | None | Best-effort gas fee estimates per token |
POST | /v1/pay/{payment_id}/support | None | Payer submits a support ticket (3/hour rate limit) |
POST /v1/payments
Create a new payment request. Requires Basic KYC.
Request body:
| Field | Type | Required | Constraints |
|---|---|---|---|
amount_usd | decimal | Yes | Greater than 0; subject to configurable global and per-token min/max limits |
description | string | No | Max 150 characters |
redirect_url | string | No | Must start with https://, max 2048 characters |
allowed_token_ids | array of UUIDs | No | Restrict payer's token choices for this payment |
Response (201 Created):
{
"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
nulluntil the payment progresses through its lifecycle.
Error codes:
| Code | Condition |
|---|---|
KYC_REQUIRED | User's KYC level is below Basic |
VALIDATION_ERROR | Invalid 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:
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by payment status (lowercase, e.g. completed) |
date_from | ISO 8601 date | Payments created on or after (UTC) |
date_to | ISO 8601 date | Payments created on or before (UTC) |
page | integer | Default: 1, min: 1 |
per_page | integer | Default: 20, min: 1, max: 100 |
Response (200 OK):
{
"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):
{
"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:
| Code | Condition |
|---|---|
NOT_FOUND | payment_id does not exist or does not belong to the authenticated user |
VALIDATION_ERROR | payment_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):
{
"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_atisnulluntil 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):
{
"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_usdoptionally constrain payments made with that specific token.
GET / PUT /v1/payments/settings
Read or update the merchant's default payment settings.
PUT request body:
{
"fee_payer": "user",
"enabled_token_ids": ["a1b2c3d4-..."]
}| Field | Type | Description |
|---|---|---|
fee_payer | "user" | "payer" | Who absorbs fees. user = merchant (default). |
enabled_token_ids | array of UUIDs | Tokens offered to payers by default. An empty array means "all active tokens". Omitted/null fields preserve the existing value. |
Response (200 OK):
{
"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:
| Field | Type | Required | Description |
|---|---|---|---|
token_id | UUID | Yes | ID of the selected token |
Response (200 OK):
{
"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:
| Code | Condition |
|---|---|
NOT_FOUND | Payment or token does not exist / token inactive |
CONFLICT | Payment not in awaiting_selection/expired_selection; unconfirmed transaction in flight; or a different token chosen after partial payment |
VALIDATION_ERROR | Token not in the allowed set for this payment; per-token min/max amount exceeded |
RATE_STALE | No 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):
{
"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):
{
"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
| Status | Description |
|---|---|
awaiting_selection | Created; payer has not yet selected a token |
awaiting_payment | Token selected; rate locked; waiting for on-chain transaction |
partially_paid | Funds received but less than required; waiting for more |
confirming | Required amount received; waiting for block confirmations |
confirmed | Block confirmations complete; sweep in progress |
completed | Payment fully settled; funds credited to merchant |
expired | Terminal expired state (admin-managed) |
expired_selection | Chain selection window expired; payer must reselect to continue |
expired_partial | Settled after partial funding; merchant credited proportionally |
blacklisted | Transaction detected from a blacklisted address |
sweep_failed | Sweep to platform wallet failed after maximum retries |
failed | System error requiring manual investigation |

