API Conventions
These rules hold across the API and are not repeated on the endpoint pages.
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
| Method | Success status |
|---|---|
GET | 200 |
POST | 201 |
DELETE | 200 |
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:
| Parameter | Default | Range |
|---|---|---|
page | 1 | from 1 |
per_page | 20 | 1 – 1000 |
A list does not return a bare array. It returns an envelope with the page metadata beside the rows:
{
"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, or400with code1001when there is no such order;GET /api/withdrawals?member_id=…&client_withdrawal_id=…— one payout object, or an empty object{}with200when there is no such payout.
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_pagegoes up to1000— 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
| Value | Wire format | Notes |
|---|---|---|
| Identifiers | JSON number | 90210. Used in paths and in query filters. |
| Client-supplied ids | JSON string | client_order_id and client_withdrawal_id are strings, not numbers. |
| Amounts, in a response | JSON string | min_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 request | string or number | amount, price and volume accept either. Sending a string avoids float rounding. |
| Booleans | real JSON boolean | active_deposit, active_withdrawal, aml_active. |
precision | JSON number | Decimal places of a network's asset. |
| Timestamps | ISO 8601 string, UTC, milliseconds | 2026-08-28T13:25:11.098Z. Same format on the way in — expired_at is sent this way. |
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.
| Operation | Your id | If you reuse it |
|---|---|---|
| Payout | client_withdrawal_id | Refused with 1001 — the first payout stands |
| Order | client_order_id | Refused 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:
| Operation | In the request | In the response |
|---|---|---|
| Transfer | asset | asset_id |
| Auto-conversion | dchain_from, asset_to | dchain_from_id, asset_to_id, plus asset_from_id |
| Withdrawal | address, address_tag | destination, destination_tag |
| Order | market | market_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.