Developers
API documentation
An API that returns the same fee breakdown as the site, line by line, with no hidden margin. This page describes the conventions shared by every endpoint, and states precisely what is open today.
What is open today
1 endpoint is actually exposed and callable: quoting. The other 5 are published as a contract, so you can build before they open, and each carries an explicit badge.
We document ahead because it helps an integration. Letting you believe it is wired would not: every endpoint states its status, and sample responses for open endpoints are responses actually obtained, not mock-ups.
Base URL and versioning
Two bases coexist: the one answering today, and the versioned API that will come with issued keys.
- Today
- /api Live
- Versioned API
- /api/v1 Published contract, not open
The version lives in the path, not in a header: a URL has to survive being pasted into a ticket. A breaking change — a removed field, a changed type, different semantics — opens a new version; the previous one stays served for at least six months, with its end date announced in the changelog. Adding a field is not breaking: your client must ignore fields it does not know.
Conventions
They hold on every endpoint, open or upcoming. Most of them exist for one reason: never lose a cent in transit.
Amounts are strings, or integers of minor units
Never a float. In JSON, 0.1 + 0.2 is not 0.3, and a large amount loses minor units on serialisation. Major-unit amounts are therefore sent as strings, and internal amounts as integers of minor units with their decimal count.
{
"net": "912.28",
"currency": "EUR",
"currencyDecimals": 2
}Decimals depend on the currency
The euro has two decimals, the CFA franc and the Vietnamese dong have none. Do not hardcode “× 100”: read currencyDecimals, or the decimals field from the reference data.
{ "amount": "125000", "currency": "XOF", "currencyDecimals": 0 }Crypto amounts are in base units
The deposit is returned with its decimal count: 8 for bitcoin, 6 for USDT, 18 for ether. The same ticker can exist with different decimals depending on the network: trust the field, not your memory.
{ "deposit": { "amount": "1000.000000", "ticker": "USDT", "decimals": 6 } }Timestamps
Dates are ISO 8601 UTC. One deliberate exception: rateAsOf is a Unix timestamp in milliseconds, because it feeds an age computation, not a display. It carries the date of the market DATA, not of your request: that distinction is what makes a stale price detectable.
{ "rateAsOf": 1788356981608, "rateSource": "coingecko" }Identifiers are stable
An asset, rail or country identifier never changes and is never reassigned. A closed rail keeps its own, with its phase and reason: your integration can see why it left your options, instead of finding a hole.
Human-facing messages are bilingual
A refusal reason is returned in French and English in the same object. You show the one matching your user, with no translation table to maintain on your side.
{ "reason": { "fr": "…", "en": "…" } }First call
No key is needed to quote. This call works as-is.
curl -sS -X POST https://fiatside.com/api/quote \
-H 'Content-Type: application/json' \
-d '{
"assetId": "usdt",
"railId": "sepa_instant",
"networkId": "tron",
"direction": "sell",
"amount": "1000"
}'{
"deposit": { "amount": "1000.000000", "ticker": "USDT", "decimals": 6 },
"gross": "921.68",
"net": "912.28",
"currency": "EUR",
"currencyDecimals": 2,
"midRate": "0.92168430",
"effectiveRate": "0.91228000",
"totalCostBps": 102,
"lines": [
{ "code": "network", "amount": "1.11", "basis": "Cout reseau tron, preleve en USDT" },
{ "code": "spread", "amount": "8.29", "basis": "0.90 % du montant converti" }
],
"settlement": { "p50Minutes": 1, "p95Minutes": 12 },
"lockSeconds": 1800,
"rateAsOf": 1788356981608,
"rateSource": "mock",
"limitError": null
}The rateSource field states where the price came from. The value “mock” is the deterministic development source, which cannot boot in production: the process refuses to start rather than quote frozen prices.
Sections
- AuthenticationHow the quote endpoint is protected today, how keys will be issued, and the rules for handling a production key.
- QuotesThe quote endpoint, its line-by-line fee breakdown, how rail limits come back in the response, and how long the rate stays locked once an order is created.
- OrdersCreating an order, idempotency key, deposit address, mandatory memo on some networks, and reading state transitions.
- Reference dataRail, asset and country catalogues, the beneficiary field schema that lets you generate your payout form, and cursor pagination.
- WebhooksEvents emitted, HMAC-SHA256 signature over the raw body, replay window, retry schedule, and why verification has to run in constant time.
- ErrorsEvery error code with its HTTP status, what it means exactly, what to do about it on the integration side, and which ones are worth retrying.
What the API will not do
The limits of an API are as useful to know as its capabilities.
- No third-party payouts. The beneficiary name must match the verified identity of the order holder, and payment rails now check name against account.
- No account creation or identity verification through the API. Those steps happen in a flow where the user sees what they accept.
- No key in a URL parameter. URLs end up in logs, referrer headers and histories: a key passed that way is a key to revoke.
- No caching of a quote on our side. The response carries Cache-Control: no-store, and your integration should not work around it: a cached quote is a stale price presented as firm.
- No crypto purchase endpoint. The service runs one way, digital asset to legal tender.