Pre-launch preview: uCrypto is not open to merchants or customers yet.

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.

Built for the sandbox, from the OpenAPI contract
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
All planned merchant API endpoints
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
With curl, using the made-up key
const response = await fetch('https://api.ucrypto.co/v1/health', {
  method: 'GET',
});
const answer = await response.json();
In TypeScript, using the made-up key
Response fields
Field Type Always sent Notes
status string Yes
{
  "status": "ok"
}
Example 200 answer, with made-up values
Error codes
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"
With curl, using the made-up key
const response = await fetch('https://api.ucrypto.co/v1/balances', {
  method: 'GET',
  headers: {
    "Authorization": "Bearer uc_test_EXAMPLE"
  },
});
const answer = await response.json();
In TypeScript, using the made-up key
Response fields
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": "…"
    }
  ]
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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"
With curl, using the made-up key
const response = await fetch('https://api.ucrypto.co/v1/transactions', {
  method: 'GET',
  headers: {
    "Authorization": "Bearer uc_test_EXAMPLE"
  },
});
const answer = await response.json();
In TypeScript, using the made-up key
Response fields
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"
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
Field Sent in Type Required
month query string No
curl https://api.ucrypto.co/v1/statements \
  -H "Authorization: Bearer uc_test_EXAMPLE"
With curl, using the made-up key
const response = await fetch('https://api.ucrypto.co/v1/statements', {
  method: 'GET',
  headers: {
    "Authorization": "Bearer uc_test_EXAMPLE"
  },
});
const answer = await response.json();
In TypeScript, using the made-up key
Response fields
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": "…"
    }
  ]
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
Field Sent in Type Required
month query string No
curl https://api.ucrypto.co/v1/checkout-sales \
  -H "Authorization: Bearer uc_test_EXAMPLE"
With curl, using the made-up key
const response = await fetch('https://api.ucrypto.co/v1/checkout-sales', {
  method: 'GET',
  headers: {
    "Authorization": "Bearer uc_test_EXAMPLE"
  },
});
const answer = await response.json();
In TypeScript, using the made-up key
Response fields
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": "…"
    }
  ]
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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"}'
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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"
}
Example 201 answer, with made-up values
Error codes
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.

Request fields
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"
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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"
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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"
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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"
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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":"…"}'
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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"
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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"
With curl, using the made-up key
const response = await fetch('https://api.ucrypto.co/v1/payouts', {
  method: 'GET',
  headers: {
    "Authorization": "Bearer uc_test_EXAMPLE"
  },
});
const answer = await response.json();
In TypeScript, using the made-up key
Response fields
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"
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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"}'
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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"
}
Example 201 answer, with made-up values
Error codes
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.

Request fields
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"
With curl, using the made-up key
const response = await fetch('https://api.ucrypto.co/v1/disputes', {
  method: 'GET',
  headers: {
    "Authorization": "Bearer uc_test_EXAMPLE"
  },
});
const answer = await response.json();
In TypeScript, using the made-up key
Response fields
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"
}
Example 200 answer, with made-up values
Error codes
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"
With curl, using the made-up key
const response = await fetch('https://api.ucrypto.co/v1/api_keys', {
  method: 'GET',
  headers: {
    "Authorization": "Bearer uc_test_EXAMPLE"
  },
});
const answer = await response.json();
In TypeScript, using the made-up key
Response fields
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"
    }
  ]
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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"]}'
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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"
}
Example 201 answer, with made-up values
Error codes
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.

Request fields
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"
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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"
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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"}'
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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
}
Example 201 answer, with made-up values
Error codes
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"
With curl, using the made-up key
const response = await fetch('https://api.ucrypto.co/v1/webhook_endpoints', {
  method: 'GET',
  headers: {
    "Authorization": "Bearer uc_test_EXAMPLE"
  },
});
const answer = await response.json();
In TypeScript, using the made-up key
Response fields
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"
    }
  ]
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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"]}'
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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"
}
Example 201 answer, with made-up values
Error codes
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.

Request fields
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"
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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"
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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"
With curl, using the made-up key
const response = await fetch('https://api.ucrypto.co/v1/events', {
  method: 'GET',
  headers: {
    "Authorization": "Bearer uc_test_EXAMPLE"
  },
});
const answer = await response.json();
In TypeScript, using the made-up key
Response fields
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"
}
Example 200 answer, with made-up values
Error codes
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.

Request fields
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"
With curl, using the made-up key
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();
In TypeScript, using the made-up key
Response fields
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
}
Example 200 answer, with made-up values
Error codes
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);
The signature check, before reading any event

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"
  }
}
Example body, with made-up values, sent with a UQT-Signature header

token.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"
  }
}
Example body, with made-up values, sent with a UQT-Signature header

redemption.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"
  }
}
Example body, with made-up values, sent with a UQT-Signature header

payout.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"
  }
}
Example body, with made-up values, sent with a UQT-Signature header

payout.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"
  }
}
Example body, with made-up values, sent with a UQT-Signature header

dispute.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"
  }
}
Example body, with made-up values, sent with a UQT-Signature header

dispute.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"
  }
}
Example body, with made-up values, sent with a UQT-Signature header

The contract this page is written from is published as openapi.json (OpenAPI 3.1), for tools that read it.

Back to developers