Authentication
API keys, their environment (Sandbox or Live) and permissions, Sandbox and Live in requests, the Idempotency-Key header, the error format, pagination and rate limits.
Last updated Oct 27, 2026
The API lives at https://api.aliquot.cc. Every merchant endpoint is under /api/v1/crypto and authenticates with an API key. So does webhook management under /api/v1/organizations/{organizationId}/webhooks (see Webhooks). The buyer-facing endpoints under /api/v1/public (checkout, payment link pages, refund claims) take no authentication: the invoice ID, link slug or claim token in the path is the credential.
Requests and responses are JSON, with snake_case field names.
API keys
Create keys in the dashboard under Developers → API keys. Give the key a name, pick its environment (Sandbox or Live, see below) and its permissions and, optionally, an expiry date. The secret is shown once, when the key is created; Aliquot stores only a hash of it, so a lost secret cannot be recovered. Create a new key and revoke the old one instead.
A key belongs to your organization and acts only on its data. You cannot give a key a permission that your own role in the organization does not have.
Revoke a key from the same page. A revoked or expired key is refused immediately.
Keys and environments
Each key is bound to one environment, chosen when you create it. Its prefix says which:
| Prefix | Environment | Accepts |
|---|---|---|
aq_sandbox_ | Sandbox | Only mode: "test" and test networks |
aq_live_ | Live | Only mode: "live" and mainnets |
In the dashboard, the key form asks for the environment (Sandbox by default). Through the API, send "environment": "sandbox" or "environment": "live" to POST /api/v1/organizations/{organizationId}/api-keys; it defaults to sandbox, or to the environment of the calling key when the request is made with a Sandbox or Live key. A key cannot create a key for the other environment: that answers 403 API_KEY_ENVIRONMENT_MISMATCH.
Every request under /api/v1/crypto made with the key stays in its environment:
- A
modequery parameter you leave out takes the key's environment: a Sandbox key lists Sandbox invoices without?mode=test. - An invoice created without
networkormodeis created in the key's environment. - A
mode,networkor payment-linkaccepted_pairsnetwork of the other environment answers403 API_KEY_ENVIRONMENT_MISMATCH, and nothing is created. - An invoice, payment link or payout of the other environment, read, voided or edited by ID, answers
404 NOT_FOUND, as if it did not exist.
Data never crosses environments: a Sandbox invoice is never listed in Live, and Sandbox balances can't be paid out to a mainnet.
Legacy keys. Keys created before environments existed start with fk_live_ and are bound to neither: they work in both, and each request picks its environment as described in Sandbox and Live in requests (lists default to live). The dashboard lists them as "Legacy · both environments". Replace them with a Sandbox or Live key.
Sending the key
Send the secret as a bearer token in the Authorization header:
curl "https://api.aliquot.cc/api/v1/crypto/invoices?mode=test" \
-H "Authorization: Bearer $ALIQUOT_API_KEY"A missing key answers 401 UNAUTHORIZED. A key that is invalid, revoked or expired answers 401 UNAUTHORIZED with the message Invalid or revoked API key. A valid key without the permission an endpoint needs answers 403 FORBIDDEN with Missing permission: <permission>.
Requests made with API keys are recorded in the request log: Developers → Logs in the dashboard, or GET /api/v1/crypto/requests. Each entry has the method, path, status, duration, the Idempotency-Key if any, and whether the response was a replay.
Permissions
| Endpoint | Permission |
|---|---|
POST /api/v1/crypto/invoices | crypto-invoices:create |
GET /api/v1/crypto/invoices, GET /api/v1/crypto/invoices/{id} | crypto-invoices:read |
POST /api/v1/crypto/invoices/{id}/void | crypto-invoices:update |
POST /api/v1/crypto/quote | crypto-invoices:read |
GET /api/v1/crypto/networks | crypto-invoices:read |
GET /api/v1/crypto/refunds | crypto-invoices:read |
GET /api/v1/crypto/overview | crypto-invoices:read |
GET /api/v1/crypto/requests | crypto-invoices:read |
POST /api/v1/crypto/payment-links | crypto-links:create |
GET /api/v1/crypto/payment-links, GET /api/v1/crypto/payment-links/{id} | crypto-links:read |
PATCH /api/v1/crypto/payment-links/{id} | crypto-links:update |
POST /api/v1/crypto/accounts | crypto-accounts:create |
GET /api/v1/crypto/accounts, GET /api/v1/crypto/accounts/{id} | crypto-accounts:read |
GET /api/v1/crypto/balances, GET /api/v1/crypto/ledger/transactions, GET /api/v1/crypto/ledger/accounts | crypto-ledger:read |
POST /api/v1/crypto/payouts | crypto-payouts:create |
GET /api/v1/crypto/payouts, GET /api/v1/crypto/payouts/{id} | crypto-payouts:read |
/api/v1/organizations/{organizationId}/webhooks/… (endpoints, deliveries, resend) | webhooks:manage |
The key form in the dashboard offers every permission in this table, grouped by resource, each with a one-line description. crypto-invoices:create, crypto-invoices:read, crypto-links:create, crypto-links:read, crypto-accounts:read and crypto-ledger:read are preselected; crypto-invoices:update, crypto-links:update, crypto-accounts:create, crypto-payouts:read, crypto-payouts:create and webhooks:manage are opt-in.
Keys that can pay out
A key with crypto-payouts:create moves money out of your balances without a per-payout code. Creating one is therefore stepped up: you need two-factor authentication enabled on your user (Settings → Security) and a current code from your authenticator app when you create the key: in the dashboard, picking crypto-payouts:create shows a field for it; through the API, send it as totp_code. Without 2FA the request answers 422 TWO_FACTOR_NOT_ENABLED; without a valid code, 403 STEP_UP_REQUIRED. A key that was not created this way answers 403 STEP_UP_REQUIRED on POST /api/v1/crypto/payouts. See Payouts.
Only crypto-payouts:create makes a key payout-capable. Permissions match exactly, so crypto-payouts:manage does not include crypto-payouts:create or crypto-payouts:read and grants no payout endpoint.
Sandbox and Live in requests
Sandbox (mode: "test" in the API) and Live are separated by network, not by endpoint:
- Creating an invoice: the
networkyou send decides the environment (BASE_SEPOLIAis Sandbox,BASE_MAINNETis Live). If you let the buyer choose the network, the invoice takes your key's environment; with a legacy key, send"mode": "test"or"mode": "live". A network and amodethat disagree are a422(with a Sandbox or Live key, the one that does not match the key is a403first). - Payment links take their environment from their
accepted_pairs, and payouts from theirnetwork. - Reading: list endpoints take a
modequery parameter, which defaults to your key's environment (livewith a legacyfk_live_key). With a legacy key, addmode=testto see Sandbox invoices, payouts, refunds, payment links, balances and ledger entries.
Every invoice, payout and payment link carries livemode: false in Sandbox, true in Live.
Idempotency
POST /api/v1/crypto/invoices and POST /api/v1/crypto/payouts require an Idempotency-Key header (1–255 characters). Use a value that identifies the operation in your system, such as an order or withdrawal ID, and reuse it when you retry.
| Situation | Response |
|---|---|
| No header, or longer than 255 characters | 400 IDEMPOTENCY_KEY_REQUIRED |
| First request with the key | The normal response (201) |
| Same key, same request, first one succeeded | The stored response, with status 200 and the header Idempotent-Replay: true |
| Same key, different method, path or body | 409 IDEMPOTENCY_KEY_REUSED |
| Same key while the first request is still running | 409 IDEMPOTENCY_IN_PROGRESS; retry shortly |
Keys are scoped to your organization. Two requests count as the same when the method, path and JSON body match; the order of keys inside the body does not matter. Only successful (2xx) responses are stored: if the first request fails, the key is released and you can correct the request and send it again with the same key.
Errors
Errors share one shape:
{
"code": "VALIDATION_FAILED",
"message": "mode (test | live) is required when no network is given",
"issues": [{ "path": "mode", "message": "mode (test | live) is required when no network is given" }]
}code is stable and meant for your code; message is for people. issues is present on validation errors and points at the field (path uses dots for nesting, such as splits.0.percent).
| Status | code | When |
|---|---|---|
| 400 | BAD_REQUEST | The body is not valid JSON. |
| 400 | IDEMPOTENCY_KEY_REQUIRED | Idempotency-Key is missing on an endpoint that needs it. |
| 401 | UNAUTHORIZED | No key, or an invalid, revoked or expired key. |
| 403 | FORBIDDEN | The key lacks the permission, or the action is not allowed. |
| 403 | STEP_UP_REQUIRED | The key was not authorized for payouts when it was created. |
| 403 | API_KEY_ENVIRONMENT_MISMATCH | A Sandbox key used for Live (a mode, network or new key of Live), or a Live key for the Sandbox. See Keys and environments. |
| 404 | NOT_FOUND | The object does not exist in your organization (or, with a Sandbox or Live key, belongs to the other environment). |
| 409 | CONFLICT | The object is in a state that does not allow the action (for example voiding a detected invoice), or a reference is already taken. |
| 409 | IDEMPOTENCY_KEY_REUSED, IDEMPOTENCY_IN_PROGRESS | See Idempotency. |
| 409 | INSUFFICIENT_BALANCE | A payout exceeds the available balance. Nothing was created. |
| 413 | PAYLOAD_TOO_LARGE | The body is larger than 1 MB. |
| 422 | VALIDATION_FAILED | A field is missing or invalid; see issues. |
| 422 | TWO_FACTOR_NOT_ENABLED | Creating a payout-capable key without 2FA. |
| 429 | RATE_LIMITED | Too many requests; see below. |
| 429 | STEP_UP_LOCKED | Too many failed two-factor codes; wait 15 minutes. |
| 500 | INTERNAL_ERROR | An unexpected error on Aliquot's side. |
| 503 | CRYPTO_NOT_CONFIGURED | Payments are unavailable on the server. |
Handle codes you do not recognize by their HTTP status.
Rate limits
Requests are counted per minute, separately for reads (GET) and writes (POST, PATCH, DELETE). The defaults are 300 reads and 60 writes per minute. Requests made with an API key count against the user who created the key; the public checkout endpoints count per IP address.
Responses carry the standard RateLimit and RateLimit-Policy headers with the limit, what remains and when the window resets. Over the limit, the API answers 429 with:
{ "code": "RATE_LIMITED", "message": "Too many requests. Try again later." }Pagination
GET on invoices, payouts, refunds, ledger transactions and the request log returns a page:
{ "items": [], "next_cursor": "inv_01JB7Q8M3X4K9TPZ2R6WVN5HDC" }Pass next_cursor back as cursor to get the next page; it is null on the last page. limit is 1–100 (default 25; 50 for the request log). Items are newest first. The other list endpoints (networks, accounts, payment links, balances, ledger accounts) return { "items": [...] } in one response.
Amounts and IDs
- Amounts are decimal strings, never numbers. USD prices have at most two decimals (
"100","100.5","100.00"); amounts in tokens are exact, with at least two decimals ("102.50","76.875"). - Percentages (
markup_percent,splits[].percent) are JSON numbers with at most two decimals (2.5is 2.5 %). - Object IDs carry a prefix:
inv_invoices,acct_accounts,po_payouts,rfd_refunds,plink_payment links,ltx_ledger transactions. They sort by creation time. - Timestamps are ISO 8601 in UTC.
Quickstart
Create an API key, create an invoice, send the buyer to the hosted checkout and verify the invoice.paid webhook, in Sandbox.
Invoices
Create, list, retrieve and void invoices; every field; quotes; and how the hosted checkout handles network selection, live status and the return to your site.