Skip to main content

API Conventions

These rules hold across the API and are not repeated on the endpoint pages.

What is documented here

These rules hold across every endpoint, so they are stated once here rather than repeated on each page.

Base URL and paths​

All requests go to https://api.example.com, and every public path carries the /api prefix:

https://api.example.com/api/withdrawals

Authentication​

Every request carries the merchant's token in the Api-Token header:

Api-Token: YOUR_API_TOKEN

One endpoint is public and needs no token: GET /api/health. Everything else requires it. See Authentication.

Status codes​

MethodSuccess status
GET200
POST201
DELETE200

A create therefore answers 201, not 200 — branch accordingly.

Content type​

Request bodies are JSON and must be sent with Content-Type: application/json.

Lists and pagination​

Every list endpoint is paginated through two query parameters:

ParameterDefaultRange
page1from 1
per_page201 – 1000

A list does not return a bare array. It returns an envelope with the page metadata beside the rows:

LIST ENVELOPE
{
"total_count": 137,
"per_page": 20,
"total_pages": 7,
"page": 1,
"prev_page": null,
"next_page": 2,
"items": []
}

prev_page and next_page are null at the ends of the range. Read the rows from items.

Paginated endpoints are the eleven collection routes: assets, markets, members, deposit addresses, orders, trades, deposits, withdrawals, auto-conversions, transfers and webhooks.

Four kinds of response are not paginated and come back as plain values:

  • single-object reads — GET /api/…/{id};
  • GET /api/members/{id}/accounts — a bare array of accounts;
  • GET /api/orders?client_order_id=… — one order object, or 400 with code 1001 when there is no such order;
  • GET /api/withdrawals?member_id=…&client_withdrawal_id=… — one payout object, or an empty object {} with 200 when there is no such payout.
Sort on your side, and take a list in one page where you can

No list endpoint applies an explicit sort, so row order is not guaranteed — not by id, not by created_at, not between two identical requests. sort and sort_by are not supported and are ignored.

Because the selection is unordered, paging through it can return the same row twice and omit others; page 2 is not "the rows after page 1".

What to do instead:

  • For a bounded set, ask for it in one page — per_page goes up to 1000 — and sort it yourself.
  • For anything larger, page through it, deduplicate by id, and accept that a single pass may be incomplete; repeat until the set stops changing.
  • Never build "the newest N" by reading page 1.

Data types​

ValueWire formatNotes
IdentifiersJSON number90210. Used in paths and in query filters.
Client-supplied idsJSON stringclient_order_id and client_withdrawal_id are strings, not numbers.
Amounts, in a responseJSON stringmin_withdrawal, min_deposit and every fee field come back as decimal strings with as many decimals as the value needs — "0.0", "5.0", "0.0001". The number of decimals is not fixed.
Amounts, in a requeststring or numberamount, price and volume accept either. Sending a string avoids float rounding.
Booleansreal JSON booleanactive_deposit, active_withdrawal, aml_active.
precisionJSON numberDecimal places of a network's asset.
TimestampsISO 8601 string, UTC, milliseconds2026-08-28T13:25:11.098Z. Same format on the way in — expired_at is sent this way.
Send amounts as strings

Every amount field accepts a string, and every amount comes back as a string. Keeping money in strings end to end removes the whole class of float-rounding bugs — "0.1" stays "0.1".

Protecting against duplicates​

Payouts and orders accept an id that you generate. Each id is unique per operation, so sending one makes a repeated request safe to issue.

OperationYour idIf you reuse it
Payoutclient_withdrawal_idRefused with 1001 — the first payout stands
Orderclient_order_idRefused with 1001 — the first order stands

Both are optional. Send them anyway: generate one id per intended operation, and reuse that same id on every retry of it.

1001 is a refusal, not a replay of the original response — it tells you the id is already used, not what the first operation did. Treat it as "this one exists" and read the record back: GET /api/orders?client_order_id=… or GET /api/withdrawals?member_id=…&client_withdrawal_id=… returns that one record. Both are direct lookups, not list calls — see Lists and pagination.

Transfers take no client id. Keep the id from the response and check for it before issuing the same transfer again.

Field naming​

The same value can be spelled differently on the way in and on the way out. The cases to know:

OperationIn the requestIn the response
Transferassetasset_id
Auto-conversiondchain_from, asset_todchain_from_id, asset_to_id, plus asset_from_id
Withdrawaladdress, address_tagdestination, destination_tag
Ordermarketmarket_id

The list filters do not follow one rule either. List Orders takes market and List Transfers takes asset — the request spelling — while List Deposits and List Withdrawals take asset_id and dchain_id, the response spelling. Each endpoint page names the filters it accepts.

Inside an object an identifier is plain id — a member is id, not member_id, and an account is id, not account_id. The member_id and account_id spellings appear only where one object points at another.

Permissions​

There is no scope or permission concept: one token does everything. See Authentication.

Errors​

A failure answers with a numeric code and a message. Branch on the code; the messages are for humans and some are composed at runtime. The full catalogue is in Errors.