Developers
API reference
The uCrypto merchant API is being built. The first table lists the routes built so far, written from its OpenAPI contract, which is tested against the API itself. The second is the full plan it is built to.
"Sandbox" means the route is built and tested for the test sandbox, which is not open yet, so nothing on this page is live.
| Method | Path | What it does | Key scope |
|---|---|---|---|
GET |
/v1/health |
Check the service and its database are answering | None |
GET |
/v1/balances |
Your token balances | read |
GET |
/v1/transactions |
Your transactions, newest first, a page at a time | read |
GET |
/v1/statements |
Your monthly statement, one per token unit, computed from the ledger | read |
GET |
/v1/checkout-sales |
Tokens customers bought at your checkout, and what has become of them | read |
POST |
/v1/redemption_intents |
Ask a customer to pay you in tokens | redeem |
GET |
/v1/redemption_intents/{id} |
One of your redemption intents | read |
POST |
/v1/redemption_intents/{id}/cancel |
Cancel an open intent | redeem |
POST |
/v1/redemption_intents/{id}/reverse |
Give all or part of a confirmed redemption back to the customer's wallet | reverse |
GET |
/v1/payouts |
Your payouts, newest first, a page at a time | read |
POST |
/v1/payouts |
Ask to be paid your available balance to your bank account | payout |
GET |
/v1/disputes |
Disputes on purchases whose tokens you received or that were made at your checkout, newest first, a page at a time | disputes |
GET |
/v1/api_keys |
List your API keys, without the keys themselves | keys |
POST |
/v1/api_keys |
Add a new API key alongside your current one | keys |
POST |
/v1/api_keys/{id}/revoke |
Revoke another of your API keys | keys |
POST |
/v1/sandbox/events |
Deliver a signed sample of a webhook event to your sandbox endpoints | read |
GET |
/v1/webhook_endpoints |
List your webhook endpoints, without their signing secrets | webhooks |
POST |
/v1/webhook_endpoints |
Add a webhook endpoint and choose the event types it receives | webhooks |
POST |
/v1/webhook_endpoints/{id}/remove |
Remove one of your webhook endpoints; nothing more is delivered to it | webhooks |
GET |
/v1/events |
List your webhook events, newest first | webhooks |
POST |
/v1/events/{id}/replay |
Re-deliver one of your webhook events to the endpoints that chose its type | webhooks |
| Method | Path | Purpose | Status |
|---|---|---|---|
POST |
/v1/redemption_intents |
Ask to be paid in tokens (amount, order ref, return URL on a verified domain) | Sandbox |
GET |
/v1/redemption_intents/{id} |
Read a redemption and its notice | Sandbox |
POST |
/v1/redemption_intents/{id}/cancel |
Cancel before the customer confirms | Sandbox |
POST |
/v1/redemption_intents/{id}/reverse |
Reverse a redemption back to the customer's wallet | Sandbox |
GET |
/v1/balances |
Pending, available and payout-locked balances | Sandbox |
GET |
/v1/transactions |
Ledger-true transaction list, identical to UI and CSV | Sandbox |
POST |
/v1/payouts |
Withdraw to a verified bank account | Sandbox |
GET |
/v1/payouts |
Payout history and status | Sandbox |
GET |
/v1/disputes |
Disputes affecting the merchant | Sandbox |
GET |
/v1/statements |
Monthly statements | Sandbox |
POST |
/v1/webhook_endpoints |
Register a signed webhook endpoint | Sandbox |
GET |
/v1/events |
List events | Sandbox |
POST |
/v1/events/{id}/replay |
Re-deliver an event | Sandbox |
Each route built for the sandbox, in the same order as the first table: what to send,
an example of what comes back, and the error codes it can answer with. Every error
answers with an error object holding its code, a
message, a doc_url linking to the code's entry in the
error codes, a request_id and, when one request
field caused it, a param naming that field.
Every amount in these examples is shown as …: uCrypto shows no
money figures on its website. Amounts are sent and answered in minor units, as each
field's notes say, and what you are charged is agreed in your application and shown in
your portal.
GET /v1/health
Check the service and its database are answering.
Needs no API key.
Needs no Idempotency-Key header.
Nothing to send but the API key.
curl https://api.ucrypto.co/v1/health
const response = await fetch('https://api.ucrypto.co/v1/health', {
method: 'GET',
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
status |
string | Yes |
{
"status": "ok"
}
| HTTP status | Code |
|---|---|
| 503 | service_unavailable |
GET /v1/balances
Your token balances.
Needs an API key with the read scope.
Needs no Idempotency-Key header.
Nothing to send but the API key.
curl https://api.ucrypto.co/v1/balances \
-H "Authorization: Bearer uc_test_EXAMPLE"
const response = await fetch('https://api.ucrypto.co/v1/balances', {
method: 'GET',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
balances |
array of object | Yes | |
balances[].unit |
string | Yes | |
balances[].available |
string | Yes | Minor units as a string of digits. |
{
"balances": [
{
"unit": "UQT.USD",
"available": "…"
}
]
}
| HTTP status | Code |
|---|---|
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 429 | rate_limited |
GET /v1/transactions
Your transactions, newest first, a page at a time.
Needs an API key with the read scope.
Needs no Idempotency-Key header.
| Field | Sent in | Type | Required |
|---|---|---|---|
limit |
query | integer | No |
cursor |
query | string | No |
curl https://api.ucrypto.co/v1/transactions \
-H "Authorization: Bearer uc_test_EXAMPLE"
const response = await fetch('https://api.ucrypto.co/v1/transactions', {
method: 'GET',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
data |
array of object | Yes | |
data[].id |
string | Yes | |
data[].type |
string | Yes | |
data[].unit |
string | Yes | |
data[].amount |
string | Yes | Minor units as a string of digits. |
data[].created_at |
string | Yes | |
data[].notice_no |
string or null | Yes | |
data[].order_ref |
string or null | Yes | |
data[].fee |
string | Yes | Minor units as a string of digits. |
next_cursor |
string or null | Yes |
{
"data": [
{
"id": "100",
"type": "string",
"unit": "UQT.USD",
"amount": "…",
"created_at": "2026-01-01T00:00:00Z",
"notice_no": "string",
"order_ref": "string",
"fee": "…"
}
],
"next_cursor": "100"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_parameter |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 429 | rate_limited |
GET /v1/statements
Your monthly statement, one per token unit, computed from the ledger.
Needs an API key with the read scope.
Needs no Idempotency-Key header.
| Field | Sent in | Type | Required |
|---|---|---|---|
month |
query | string | No |
curl https://api.ucrypto.co/v1/statements \
-H "Authorization: Bearer uc_test_EXAMPLE"
const response = await fetch('https://api.ucrypto.co/v1/statements', {
method: 'GET',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
month |
string | Yes | |
data |
array of object | Yes | |
data[].unit |
string | Yes | |
data[].currency |
string | Yes | |
data[].face_value |
string | Yes | What one token is worth in the currency. |
data[].opening_balance |
string | Yes | Minor units as a string of digits. |
data[].redemptions |
string | Yes | Minor units as a string of digits. |
data[].fees |
string | Yes | Minor units as a string of digits. |
data[].reversals |
string | Yes | Minor units as a string of digits. |
data[].payouts |
string | Yes | Minor units as a string of digits. |
data[].closing_balance |
string | Yes | Minor units as a string of digits. |
{
"month": "2026-01",
"data": [
{
"unit": "UQT.USD",
"currency": "USD",
"face_value": "0.01",
"opening_balance": "…",
"redemptions": "…",
"fees": "…",
"reversals": "…",
"payouts": "…",
"closing_balance": "…"
}
]
}
| HTTP status | Code |
|---|---|
| 400 | invalid_parameter |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 429 | rate_limited |
GET /v1/checkout-sales
Tokens customers bought at your checkout, and what has become of them.
Needs an API key with the read scope.
Needs no Idempotency-Key header.
| Field | Sent in | Type | Required |
|---|---|---|---|
month |
query | string | No |
curl https://api.ucrypto.co/v1/checkout-sales \
-H "Authorization: Bearer uc_test_EXAMPLE"
const response = await fetch('https://api.ucrypto.co/v1/checkout-sales', {
method: 'GET',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
month |
string | Yes | |
data |
array of object | Yes | |
data[].id |
string | Yes | |
data[].sold_at |
string | Yes | |
data[].unit |
string | Yes | |
data[].currency |
string | Yes | |
data[].amount |
string | Yes | Tokens the customer bought at your checkout. |
data[].credited |
boolean | Yes | Always false: you are credited only for tokens spent with you, as redemptions. |
data[].spent_with_you |
string | Yes | Minor units as a string of digits. |
data[].spent_elsewhere |
string | Yes | Spent with other merchants, added up; which merchants is never given. |
data[].not_yet_spent |
string | Yes | Minor units as a string of digits. |
data[].refunded |
string | Yes | Refunded to the customer's card, charged back or reversed. |
{
"month": "2026-01",
"data": [
{
"id": "100",
"sold_at": "2026-01-01T00:00:00Z",
"unit": "UQT.USD",
"currency": "USD",
"amount": "…",
"credited": false,
"spent_with_you": "…",
"spent_elsewhere": "…",
"not_yet_spent": "…",
"refunded": "…"
}
]
}
| HTTP status | Code |
|---|---|
| 400 | invalid_parameter |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 429 | rate_limited |
POST /v1/redemption_intents
Ask a customer to pay you in tokens.
Needs an API key with the redeem scope.
Needs an Idempotency-Key header: a repeat with the same key does nothing twice.
| Field | Sent in | Type | Required |
|---|---|---|---|
amount |
body | integer or string | Yes |
unit |
body | string | Yes |
order_ref |
body | string | Yes |
return_url |
body | string | Yes |
curl -X POST https://api.ucrypto.co/v1/redemption_intents \
-H "Authorization: Bearer uc_test_EXAMPLE" \
-H "Idempotency-Key: order-1001-try-1" \
-H "Content-Type: application/json" \
-d '{"amount":"…","unit":"UQT.USD","order_ref":"order-1001","return_url":"https://shop.example/thanks"}'
const response = await fetch('https://api.ucrypto.co/v1/redemption_intents', {
method: 'POST',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE",
"Idempotency-Key": "order-1001-try-1",
"Content-Type": "application/json"
},
body: JSON.stringify({
"amount": "…",
"unit": "UQT.USD",
"order_ref": "order-1001",
"return_url": "https://shop.example/thanks"
}),
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
unit |
string | Yes | |
amount |
string | Yes | Minor units as a string of digits. |
order_ref |
string | Yes | |
return_url |
string | Yes | |
status |
string | Yes | |
created_at |
string | Yes | |
expires_at |
string | Yes | |
confirmed_at |
string or null | Yes | |
cancelled_at |
string or null | Yes |
{
"id": "00000000-0000-4000-8000-000000000000",
"unit": "UQT.USD",
"amount": "…",
"order_ref": "string",
"return_url": "string",
"status": "open",
"created_at": "2026-01-01T00:00:00Z",
"expires_at": "2026-01-01T00:00:00Z",
"confirmed_at": "2026-01-01T00:00:00Z",
"cancelled_at": "2026-01-01T00:00:00Z"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_json |
| 400 | invalid_number |
| 400 | invalid_amount |
| 400 | invalid_parameter |
| 400 | missing_idempotency_key |
| 400 | invalid_idempotency_key |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 409 | idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | idempotency_key_reused |
| 422 | return_url_not_verified |
| 422 | merchant_not_eligible |
| 422 | currency_not_accepted |
| 429 | rate_limited |
GET /v1/redemption_intents/{id}
One of your redemption intents.
Needs an API key with the read scope.
Needs no Idempotency-Key header.
| Field | Sent in | Type | Required |
|---|---|---|---|
id |
path | string | Yes |
curl https://api.ucrypto.co/v1/redemption_intents/00000000-0000-4000-8000-000000000001 \
-H "Authorization: Bearer uc_test_EXAMPLE"
const response = await fetch('https://api.ucrypto.co/v1/redemption_intents/00000000-0000-4000-8000-000000000001', {
method: 'GET',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
unit |
string | Yes | |
amount |
string | Yes | Minor units as a string of digits. |
order_ref |
string | Yes | |
return_url |
string | Yes | |
status |
string | Yes | |
created_at |
string | Yes | |
expires_at |
string | Yes | |
confirmed_at |
string or null | Yes | |
cancelled_at |
string or null | Yes |
{
"id": "00000000-0000-4000-8000-000000000000",
"unit": "UQT.USD",
"amount": "…",
"order_ref": "string",
"return_url": "string",
"status": "open",
"created_at": "2026-01-01T00:00:00Z",
"expires_at": "2026-01-01T00:00:00Z",
"confirmed_at": "2026-01-01T00:00:00Z",
"cancelled_at": "2026-01-01T00:00:00Z"
}
| HTTP status | Code |
|---|---|
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 404 | not_found |
| 429 | rate_limited |
POST /v1/redemption_intents/{id}/cancel
Cancel an open intent.
Needs an API key with the redeem scope.
Needs an Idempotency-Key header: a repeat with the same key does nothing twice.
| Field | Sent in | Type | Required |
|---|---|---|---|
id |
path | string | Yes |
curl -X POST https://api.ucrypto.co/v1/redemption_intents/00000000-0000-4000-8000-000000000001/cancel \
-H "Authorization: Bearer uc_test_EXAMPLE" \
-H "Idempotency-Key: order-1001-try-1"
const response = await fetch('https://api.ucrypto.co/v1/redemption_intents/00000000-0000-4000-8000-000000000001/cancel', {
method: 'POST',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE",
"Idempotency-Key": "order-1001-try-1"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
unit |
string | Yes | |
amount |
string | Yes | Minor units as a string of digits. |
order_ref |
string | Yes | |
return_url |
string | Yes | |
status |
string | Yes | |
created_at |
string | Yes | |
expires_at |
string | Yes | |
confirmed_at |
string or null | Yes | |
cancelled_at |
string or null | Yes |
{
"id": "00000000-0000-4000-8000-000000000000",
"unit": "UQT.USD",
"amount": "…",
"order_ref": "string",
"return_url": "string",
"status": "open",
"created_at": "2026-01-01T00:00:00Z",
"expires_at": "2026-01-01T00:00:00Z",
"confirmed_at": "2026-01-01T00:00:00Z",
"cancelled_at": "2026-01-01T00:00:00Z"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_json |
| 400 | invalid_number |
| 400 | missing_idempotency_key |
| 400 | invalid_idempotency_key |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 404 | not_found |
| 409 | redemption_intent_not_open |
| 409 | idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | idempotency_key_reused |
| 429 | rate_limited |
POST /v1/redemption_intents/{id}/reverse
Give all or part of a confirmed redemption back to the customer's wallet.
Needs an API key with the reverse scope.
Needs an Idempotency-Key header: a repeat with the same key does nothing twice.
| Field | Sent in | Type | Required |
|---|---|---|---|
id |
path | string | Yes |
amount |
body | integer or string | Yes |
curl -X POST https://api.ucrypto.co/v1/redemption_intents/00000000-0000-4000-8000-000000000001/reverse \
-H "Authorization: Bearer uc_test_EXAMPLE" \
-H "Idempotency-Key: order-1001-try-1" \
-H "Content-Type: application/json" \
-d '{"amount":"…"}'
const response = await fetch('https://api.ucrypto.co/v1/redemption_intents/00000000-0000-4000-8000-000000000001/reverse', {
method: 'POST',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE",
"Idempotency-Key": "order-1001-try-1",
"Content-Type": "application/json"
},
body: JSON.stringify({
"amount": "…"
}),
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
redemption_intent |
string | Yes | |
notice_no |
string | Yes | |
unit |
string | Yes | |
amount |
string | Yes | Minor units as a string of digits. |
status |
string | Yes |
{
"id": "100",
"redemption_intent": "00000000-0000-4000-8000-000000000000",
"notice_no": "string",
"unit": "UQT.USD",
"amount": "…",
"status": "pending"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_json |
| 400 | invalid_number |
| 400 | invalid_amount |
| 400 | missing_idempotency_key |
| 400 | invalid_idempotency_key |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 403 | account_unavailable |
| 404 | not_found |
| 409 | redemption_not_reversible |
| 409 | idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | reversal_exceeds_redeemed |
| 422 | idempotency_key_reused |
| 429 | rate_limited |
GET /v1/payouts
Your payouts, newest first, a page at a time.
Needs an API key with the read scope.
Needs no Idempotency-Key header.
| Field | Sent in | Type | Required |
|---|---|---|---|
limit |
query | integer | No |
cursor |
query | string | No |
curl https://api.ucrypto.co/v1/payouts \
-H "Authorization: Bearer uc_test_EXAMPLE"
const response = await fetch('https://api.ucrypto.co/v1/payouts', {
method: 'GET',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
data |
array of object | Yes | |
data[].id |
string | Yes | |
data[].unit |
string | Yes | |
data[].amount |
string | Yes | Minor units as a string of digits. |
data[].fee |
string | Yes | Minor units as a string of digits. |
data[].net_payout |
string | Yes | Minor units as a string of digits. |
data[].bank_account |
string or null | Yes | |
data[].status |
string | Yes | |
data[].created_at |
string | Yes | |
data[].updated_at |
string | Yes | |
next_cursor |
string or null | Yes |
{
"data": [
{
"id": "100",
"unit": "UQT.USD",
"amount": "…",
"fee": "…",
"net_payout": "…",
"bank_account": "string",
"status": "processing",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}
],
"next_cursor": "100"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_parameter |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 429 | rate_limited |
POST /v1/payouts
Ask to be paid your available balance to your bank account.
Needs an API key with the payout scope.
Needs an Idempotency-Key header: a repeat with the same key does nothing twice.
| Field | Sent in | Type | Required |
|---|---|---|---|
unit |
body | string | Yes |
amount |
body | integer or string | Yes |
bank_account |
body | string | Yes |
curl -X POST https://api.ucrypto.co/v1/payouts \
-H "Authorization: Bearer uc_test_EXAMPLE" \
-H "Idempotency-Key: order-1001-try-1" \
-H "Content-Type: application/json" \
-d '{"unit":"UQT.USD","amount":"…","bank_account":"SANDBOX-ACCOUNT"}'
const response = await fetch('https://api.ucrypto.co/v1/payouts', {
method: 'POST',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE",
"Idempotency-Key": "order-1001-try-1",
"Content-Type": "application/json"
},
body: JSON.stringify({
"unit": "UQT.USD",
"amount": "…",
"bank_account": "SANDBOX-ACCOUNT"
}),
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
unit |
string | Yes | |
amount |
string | Yes | Minor units as a string of digits. |
fee |
string | Yes | Minor units as a string of digits. |
net_payout |
string | Yes | Minor units as a string of digits. |
bank_account |
string | Yes | |
status |
string | Yes |
{
"id": "100",
"unit": "UQT.USD",
"amount": "…",
"fee": "…",
"net_payout": "…",
"bank_account": "string",
"status": "pending"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_json |
| 400 | invalid_number |
| 400 | invalid_amount |
| 400 | invalid_parameter |
| 400 | missing_idempotency_key |
| 400 | invalid_idempotency_key |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 403 | account_unavailable |
| 409 | idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | idempotency_key_reused |
| 422 | insufficient_balance |
| 422 | balance_in_settlement_hold |
| 422 | bank_account_not_found |
| 422 | bank_account_blocked |
| 422 | currency_mismatch |
| 422 | merchant_not_eligible |
| 429 | rate_limited |
| 503 | service_unavailable |
GET /v1/disputes
Disputes on purchases whose tokens you received or that were made at your checkout, newest first, a page at a time.
Needs an API key with the disputes scope.
Needs no Idempotency-Key header.
| Field | Sent in | Type | Required |
|---|---|---|---|
limit |
query | integer | No |
cursor |
query | string | No |
curl https://api.ucrypto.co/v1/disputes \
-H "Authorization: Bearer uc_test_EXAMPLE"
const response = await fetch('https://api.ucrypto.co/v1/disputes', {
method: 'GET',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
data |
array of object | Yes | |
data[].id |
string | Yes | Names the dispute; the cursor for the next page. |
data[].receipt_no |
string | Yes | |
data[].amount |
string | Yes | The disputed amount, in the card currency's minor units. |
data[].currency |
string | Yes | |
data[].status |
string | Yes | |
data[].deadline |
string or null | Yes | The day (UTC) uCrypto must answer the dispute by; null when the market sets none. |
data[].received_at |
string | Yes | |
next_cursor |
string or null | Yes |
{
"data": [
{
"id": "string",
"receipt_no": "string",
"amount": "…",
"currency": "USD",
"status": "open",
"deadline": "2026-01-31",
"received_at": "2026-01-01T00:00:00Z"
}
],
"next_cursor": "string"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_parameter |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 429 | rate_limited |
GET /v1/api_keys
List your API keys, without the keys themselves.
Needs an API key with the keys scope.
Needs no Idempotency-Key header.
Nothing to send but the API key.
curl https://api.ucrypto.co/v1/api_keys \
-H "Authorization: Bearer uc_test_EXAMPLE"
const response = await fetch('https://api.ucrypto.co/v1/api_keys', {
method: 'GET',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
data |
array of object | Yes | |
data[].id |
string | Yes | |
data[].mode |
string | Yes | A sandbox key works only until the account goes live; a live key only after. |
data[].scopes |
array of string | Yes | |
data[].created_at |
string | Yes | |
data[].revoked_at |
string or null | Yes |
{
"data": [
{
"id": "100",
"mode": "sandbox",
"scopes": [
"string"
],
"created_at": "2026-01-01T00:00:00Z",
"revoked_at": "2026-01-01T00:00:00Z"
}
]
}
| HTTP status | Code |
|---|---|
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 429 | rate_limited |
POST /v1/api_keys
Add a new API key alongside your current one.
Needs an API key with the keys scope.
Needs an Idempotency-Key header: a repeat with the same key does nothing twice.
| Field | Sent in | Type | Required |
|---|---|---|---|
scopes |
body | array of string | Yes |
curl -X POST https://api.ucrypto.co/v1/api_keys \
-H "Authorization: Bearer uc_test_EXAMPLE" \
-H "Idempotency-Key: order-1001-try-1" \
-H "Content-Type: application/json" \
-d '{"scopes":["read"]}'
const response = await fetch('https://api.ucrypto.co/v1/api_keys', {
method: 'POST',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE",
"Idempotency-Key": "order-1001-try-1",
"Content-Type": "application/json"
},
body: JSON.stringify({
"scopes": [
"read"
]
}),
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
mode |
string | Yes | The calling key's mode: a sandbox key makes sandbox keys, a live key live keys. |
scopes |
array of string | Yes | |
created_at |
string | Yes | |
revoked_at |
string or null | Yes | |
key |
string or null | Yes | The new API key, shown only in the first answer; null when the call is repeated with the same Idempotency-Key. |
{
"id": "100",
"mode": "sandbox",
"scopes": [
"string"
],
"created_at": "2026-01-01T00:00:00Z",
"revoked_at": "2026-01-01T00:00:00Z",
"key": "string"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_json |
| 400 | invalid_number |
| 400 | invalid_parameter |
| 400 | missing_idempotency_key |
| 400 | invalid_idempotency_key |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 409 | idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | idempotency_key_reused |
| 429 | rate_limited |
POST /v1/api_keys/{id}/revoke
Revoke another of your API keys.
Needs an API key with the keys scope.
Needs an Idempotency-Key header: a repeat with the same key does nothing twice.
| Field | Sent in | Type | Required |
|---|---|---|---|
id |
path | string | Yes |
curl -X POST https://api.ucrypto.co/v1/api_keys/2/revoke \
-H "Authorization: Bearer uc_test_EXAMPLE" \
-H "Idempotency-Key: order-1001-try-1"
const response = await fetch('https://api.ucrypto.co/v1/api_keys/2/revoke', {
method: 'POST',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE",
"Idempotency-Key": "order-1001-try-1"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
mode |
string | Yes | A sandbox key works only until the account goes live; a live key only after. |
scopes |
array of string | Yes | |
created_at |
string | Yes | |
revoked_at |
string or null | Yes |
{
"id": "100",
"mode": "sandbox",
"scopes": [
"string"
],
"created_at": "2026-01-01T00:00:00Z",
"revoked_at": "2026-01-01T00:00:00Z"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_json |
| 400 | invalid_number |
| 400 | missing_idempotency_key |
| 400 | invalid_idempotency_key |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 404 | not_found |
| 409 | api_key_in_use |
| 409 | idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | idempotency_key_reused |
| 429 | rate_limited |
POST /v1/sandbox/events
Deliver a signed sample of a webhook event to your sandbox endpoints.
Needs an API key with the read scope.
Needs an Idempotency-Key header: a repeat with the same key does nothing twice.
| Field | Sent in | Type | Required |
|---|---|---|---|
type |
body | string | Yes |
curl -X POST https://api.ucrypto.co/v1/sandbox/events \
-H "Authorization: Bearer uc_test_EXAMPLE" \
-H "Idempotency-Key: order-1001-try-1" \
-H "Content-Type: application/json" \
-d '{"type":"token.purchased"}'
const response = await fetch('https://api.ucrypto.co/v1/sandbox/events', {
method: 'POST',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE",
"Idempotency-Key": "order-1001-try-1",
"Content-Type": "application/json"
},
body: JSON.stringify({
"type": "token.purchased"
}),
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
type |
string | Yes | |
simulated |
boolean | Yes | |
endpoints |
integer | Yes |
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "token.purchased",
"simulated": true,
"endpoints": 0
}
| HTTP status | Code |
|---|---|
| 400 | invalid_json |
| 400 | invalid_number |
| 400 | invalid_parameter |
| 400 | missing_idempotency_key |
| 400 | invalid_idempotency_key |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 403 | sandbox_only |
| 409 | idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | idempotency_key_reused |
| 429 | rate_limited |
GET /v1/webhook_endpoints
List your webhook endpoints, without their signing secrets.
Needs an API key with the webhooks scope.
Needs no Idempotency-Key header.
Nothing to send but the API key.
curl https://api.ucrypto.co/v1/webhook_endpoints \
-H "Authorization: Bearer uc_test_EXAMPLE"
const response = await fetch('https://api.ucrypto.co/v1/webhook_endpoints', {
method: 'GET',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
data |
array of object | Yes | |
data[].id |
string | Yes | |
data[].url |
string | Yes | |
data[].event_types |
array of string | Yes | |
data[].created_at |
string | Yes | |
data[].removed_at |
string or null | Yes |
{
"data": [
{
"id": "100",
"url": "string",
"event_types": [
"token.purchased"
],
"created_at": "2026-01-01T00:00:00Z",
"removed_at": "2026-01-01T00:00:00Z"
}
]
}
| HTTP status | Code |
|---|---|
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 429 | rate_limited |
POST /v1/webhook_endpoints
Add a webhook endpoint and choose the event types it receives.
Needs an API key with the webhooks scope.
Needs an Idempotency-Key header: a repeat with the same key does nothing twice.
| Field | Sent in | Type | Required |
|---|---|---|---|
url |
body | string | Yes |
event_types |
body | array of string | Yes |
curl -X POST https://api.ucrypto.co/v1/webhook_endpoints \
-H "Authorization: Bearer uc_test_EXAMPLE" \
-H "Idempotency-Key: order-1001-try-1" \
-H "Content-Type: application/json" \
-d '{"url":"https://shop.example/uqt-webhooks","event_types":["token.purchased"]}'
const response = await fetch('https://api.ucrypto.co/v1/webhook_endpoints', {
method: 'POST',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE",
"Idempotency-Key": "order-1001-try-1",
"Content-Type": "application/json"
},
body: JSON.stringify({
"url": "https://shop.example/uqt-webhooks",
"event_types": [
"token.purchased"
]
}),
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
url |
string | Yes | |
event_types |
array of string | Yes | |
created_at |
string | Yes | |
removed_at |
string or null | Yes | |
signing_secret |
string or null | Yes | The endpoint's signing secret (whsec_…), shown only in the first answer; null when the call is repeated with the same Idempotency-Key. |
{
"id": "100",
"url": "string",
"event_types": [
"token.purchased"
],
"created_at": "2026-01-01T00:00:00Z",
"removed_at": "2026-01-01T00:00:00Z",
"signing_secret": "string"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_json |
| 400 | invalid_number |
| 400 | invalid_parameter |
| 400 | missing_idempotency_key |
| 400 | invalid_idempotency_key |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 409 | idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | idempotency_key_reused |
| 429 | rate_limited |
| 503 | service_unavailable |
POST /v1/webhook_endpoints/{id}/remove
Remove one of your webhook endpoints; nothing more is delivered to it.
Needs an API key with the webhooks scope.
Needs an Idempotency-Key header: a repeat with the same key does nothing twice.
| Field | Sent in | Type | Required |
|---|---|---|---|
id |
path | string | Yes |
curl -X POST https://api.ucrypto.co/v1/webhook_endpoints/1/remove \
-H "Authorization: Bearer uc_test_EXAMPLE" \
-H "Idempotency-Key: order-1001-try-1"
const response = await fetch('https://api.ucrypto.co/v1/webhook_endpoints/1/remove', {
method: 'POST',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE",
"Idempotency-Key": "order-1001-try-1"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
url |
string | Yes | |
event_types |
array of string | Yes | |
created_at |
string | Yes | |
removed_at |
string or null | Yes |
{
"id": "100",
"url": "string",
"event_types": [
"token.purchased"
],
"created_at": "2026-01-01T00:00:00Z",
"removed_at": "2026-01-01T00:00:00Z"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_json |
| 400 | invalid_number |
| 400 | missing_idempotency_key |
| 400 | invalid_idempotency_key |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 404 | not_found |
| 409 | idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | idempotency_key_reused |
| 429 | rate_limited |
GET /v1/events
List your webhook events, newest first.
Needs an API key with the webhooks scope.
Needs no Idempotency-Key header.
| Field | Sent in | Type | Required |
|---|---|---|---|
limit |
query | integer | No |
cursor |
query | string | No |
curl https://api.ucrypto.co/v1/events \
-H "Authorization: Bearer uc_test_EXAMPLE"
const response = await fetch('https://api.ucrypto.co/v1/events', {
method: 'GET',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
data |
array of object | Yes | |
data[].id |
string | Yes | |
data[].type |
string | Yes | |
data[].created_at |
string | Yes | |
data[].data |
object | Yes | The event's data, exactly as it is delivered to your endpoints. |
next_cursor |
string or null | Yes |
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "token.purchased",
"created_at": "2026-01-01T00:00:00Z",
"data": {}
}
],
"next_cursor": "string"
}
| HTTP status | Code |
|---|---|
| 400 | invalid_parameter |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 429 | rate_limited |
POST /v1/events/{id}/replay
Queues the event again for each of your active endpoints that chose its type, even if it was delivered or given up. Events can be replayed for 30 days (D70); an older event is refused with 422 webhook_event_too_old and nothing is queued.
Needs an API key with the webhooks scope.
Needs an Idempotency-Key header: a repeat with the same key does nothing twice.
| Field | Sent in | Type | Required |
|---|---|---|---|
id |
path | string | Yes |
curl -X POST https://api.ucrypto.co/v1/events/00000000-0000-4000-8000-000000000002/replay \
-H "Authorization: Bearer uc_test_EXAMPLE" \
-H "Idempotency-Key: order-1001-try-1"
const response = await fetch('https://api.ucrypto.co/v1/events/00000000-0000-4000-8000-000000000002/replay', {
method: 'POST',
headers: {
"Authorization": "Bearer uc_test_EXAMPLE",
"Idempotency-Key": "order-1001-try-1"
},
});
const answer = await response.json();
| Field | Type | Always sent | Notes |
|---|---|---|---|
id |
string | Yes | |
endpoints |
integer | Yes | How many of your active endpoints that chose this event's type it was queued for. |
{
"id": "00000000-0000-4000-8000-000000000000",
"endpoints": 0
}
| HTTP status | Code |
|---|---|
| 400 | invalid_json |
| 400 | invalid_number |
| 400 | missing_idempotency_key |
| 400 | invalid_idempotency_key |
| 401 | missing_api_key |
| 401 | invalid_api_key |
| 403 | insufficient_scope |
| 403 | merchant_not_active |
| 403 | api_key_wrong_mode |
| 404 | not_found |
| 409 | idempotency_key_in_use |
| 413 | payload_too_large |
| 422 | webhook_event_too_old |
| 422 | idempotency_key_reused |
| 429 | rate_limited |
Webhook events
Each event is sent to your endpoint as a POST with a JSON body holding its
id, type, created_at and data.
Check its UQT-Signature header before you act on it, with the
verify function from the
signature guide:
// Read the body as text, exactly as it arrived, before parsing it.
const body = await request.text();
const header = request.headers.get('UQT-Signature');
if (!verify(signingSecret, body, header)) return new Response(null, { status: 400 });
const event = JSON.parse(body);
token.purchased
Sent when a customer bought tokens through your checkout.
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "token.purchased",
"created_at": "2026-01-01T00:00:00Z",
"data": {
"receipt_no": "R-SANDBOX-1",
"intent_id": "00000000-0000-4000-8000-000000000001",
"unit": "UQT.USD",
"amount": "…",
"transaction_id": "1",
"purchased_at": "2026-01-01T00:00:00.000Z"
}
}
UQT-Signature headertoken.redeemed
Sent when a customer paid you with tokens.
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "token.redeemed",
"created_at": "2026-01-01T00:00:00Z",
"data": {
"notice_no": "N-SANDBOX-1",
"order_ref": "order-sandbox-1",
"unit": "UQT.USD",
"amount": "…",
"fee": "…",
"net_amount": "1960",
"transaction_id": "1",
"redeemed_at": "2026-01-01T00:00:00.000Z"
}
}
UQT-Signature headerredemption.reversed
Sent when a payment to you was reversed.
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "redemption.reversed",
"created_at": "2026-01-01T00:00:00Z",
"data": {
"reversal_id": "1",
"notice_no": "N-SANDBOX-1",
"order_ref": "order-sandbox-1",
"unit": "UQT.USD",
"amount": "…",
"transaction_id": "1",
"reversed_at": "2026-01-01T00:00:00.000Z"
}
}
UQT-Signature headerpayout.paid
Sent when a payout reached your bank.
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "payout.paid",
"created_at": "2026-01-01T00:00:00Z",
"data": {
"settlement_id": "1",
"unit": "UQT.USD",
"amount": "…",
"fee": "…",
"net_payout": "…",
"bank_account": "SANDBOX-ACCOUNT",
"transaction_id": "1",
"paid_at": "2026-01-01T00:00:00.000Z"
}
}
UQT-Signature headerpayout.failed
Sent when a payout was not paid or came back.
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "payout.failed",
"created_at": "2026-01-01T00:00:00Z",
"data": {
"settlement_id": "1",
"status": "rejected",
"unit": "UQT.USD",
"amount": "…",
"fee": "…",
"credited": "2000",
"bank_account": "SANDBOX-ACCOUNT",
"transaction_id": "1",
"failed_at": "2026-01-01T00:00:00.000Z"
}
}
UQT-Signature headerdispute.opened
Sent when a customer disputed a payment.
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "dispute.opened",
"created_at": "2026-01-01T00:00:00Z",
"data": {
"chargeback_no": "CB-SANDBOX-1",
"receipt_no": "R-SANDBOX-1",
"reason_code": "10.4",
"unit": "UQT.USD",
"amount": "…",
"transaction_id": "1",
"opened_at": "2026-01-01T00:00:00.000Z"
}
}
UQT-Signature headerdispute.closed
Sent when a dispute was closed.
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "dispute.closed",
"created_at": "2026-01-01T00:00:00Z",
"data": {
"chargeback_no": "CB-SANDBOX-1",
"receipt_no": "R-SANDBOX-1",
"outcome": "won",
"unit": "UQT.USD",
"amount": "…",
"transaction_id": "1",
"closed_at": "2026-01-01T00:00:00.000Z"
}
}
UQT-Signature headerThe contract this page is written from is published as openapi.json (OpenAPI 3.1), for tools that read it.