- Non-custodial. We never hold the funds and never sign: the transaction we return is unsigned and your user’s wallet signs it. Nothing sits with us between the payer and the merchant.
- Stateless. There are no server-side sessions. Each call is keyed by the ids
the previous call returned (
orderId,provider,payload,txHash,transactionId,accountId). Your client holds the thread.
Which products it covers
Card issuing is documented separately - see Card issuing.
Reference lookups cover all of them: read the supported countries, currencies,
limits, rates, and statuses instead of hard-coding them.
Full request and response shapes are in the
API reference.
Authentication
Every call uses an API key against the/v1 prefix. There is one kind of key: it
carries its products, corridors and rate, and works from your server or a page
alike, so keep it where only you can read it. What a key is allowed to settle -
which chains and tokens as a destination - is part of your account setup, not
something you pass per request. The header and what a refusal looks like are on
Authentication.
What your key carries
Read your own setup rather than asking us for it.GET /v1/config answers it,
narrowed to your key: the products you may call, your maxExecutionUsd, and
under crypto the chains and tokens you may name. Call it first and build your
corridor list from it instead of keeping a copy.
A product not on your key is refused, never defaulted: its endpoints answer
403 permission_denied naming the product. To use a product that is not on
your key yet, ask us to add it - one configuration change on our side.
Source and destination are set separately on purpose: taking payment from a
chain and settling on it are different things, and you rarely want both
everywhere. A quote names both sides in full - destination.chain and
destination.token are required, and a chain your key is not enabled for is
refused with 403 chain_not_allowed.
These corridors describe your account setup, not a security boundary. They
keep your integration inside what we agreed and give you a clear error when a
request falls outside it. If you need something locked down, lock it down on
your side too - we will happily match it here, but do not rely on this as your
only control.
Read the lists before you call anything
The reference lists come first in the API reference because you need them before the first call: which countries can be onboarded, which chains, tokens and fiat currencies exist, what every status means and which one to credit on, and the per-rail limits. Start at Countries and read them from the API rather than keeping a copy.Which environment answers
Every response starts withenvironment: sandbox or production, derived from
the upstreams this deployment is configured against. The products do not share
one: crypto deposits run on production with real money, cards and bank transfers
run on the providers’ sandboxes. Each product page in this documentation and
each endpoint page in the reference says which applies to it.
The flow
The walkthrough below is the crypto deposit path - the same three calls the widget makes, on the same rails. The other products follow the same shape: price it, start it, track it. Their calls are on the product pages linked above.1
Price it
POST /v1/deposit/quote with where the money comes from, where it should
land, and the amount.Decide which side the amount pins down. exact-in means the payer spends
exactly that and whatever arrives, arrives. target-out (exact out) means a
fixed figure must land and the payer’s side flexes to cover it - this is the
one you want when you are collecting a set price.See Quote a deposit.2
Build the transaction
POST /v1/deposit/transaction with the quote you got back. You receive an
unsigned transaction, and an approval transaction before it when the token
allowance is not enough.See Build the transaction.3
Let the user sign
Your frontend submits it from the user’s wallet. Nothing reaches us here.
4
Track it
POST /v1/deposit/status with the receipt from the submitted transaction.
Poll it until it is done, or take the webhook.See Check status.Quotes
A quote prices the route and reserves liquidity, so it is only good for a short window and the window is short on purpose. Two things follow for your UI:- Show the user how long is left, and give them a way to ask for a fresh quote.
- Do not build a transaction from a quote you have been sitting on. Re-quote and build from the new one.
Naming the destination
Name bothdestination.chain and destination.token on the quote - a chain
without its token address is refused rather than guessed. Read the chains and
tokens your key may use from
GET /v1/config.
Your key has to be enabled for that chain as a destination. If it is not, you get
403 chain_not_allowed rather than a quote you could not have used. That is your
account setup talking, not a security control - see
What your key carries.
Payment methods
What a user can pay with, per product:- Stablecoins from a connected wallet, on any supported network - see Coverage.
- Cards, priced before the user commits to anything.
- Bank transfers into a virtual account: local rails and international wires,
depending on the currency. The per-currency list is on Coverage and
served by
GET /v1/currencies.
Statuses
Each product has its own small status set: deposits reportpending, done,
failed, or expired; cards report pending, processing, completed, failed,
or unknown; bank transfers report awaiting_deposit or funded. In every set
there is exactly one state that means “credit the user” - done, completed,
funded - and you credit on that state only. A partner state we do not recognise is
reported as unknown and flagged, never quietly shown as “in progress”. See
Transaction statuses.
Rate limits
Your key carries a request ceiling, agreed when it is issued and counted per key, never per IP: every request from you reaches us from your servers, so we see one caller, not your users. Over the limit returns429 rate_limited. Design polling
and retries to stay under it - for status polling, a few seconds between calls is
plenty - and handle 429 by backing off rather than retrying immediately.
Limiting your own users is yours to do. You know who they are - they are
logged in to your product - and we do not. If one of them hammers your
integration, they will spend your ceiling and your other users will feel it.
If you are onboarding in bursts, tell us and we will raise your ceiling rather
than let you discover it in production.
Amount limits
Amount limits are per rail and per provider. The stablecoin path is bounded by live liquidity on the route, which is why a quote can come back as no route available - that is a normal, temporary answer, not an error to retry in a loop. Fiat minimums and maximums are per currency and rail - read them fromGET /v1/limits.
Errors
Every error comes back in the same envelope, with acode to branch on and a
message for your logs. The envelope, the full code table, and the one exception
(bank transfer and virtual account endpoints pass the banking partner’s own codes
through unchanged) are on Errors.
Before you go live
Handle
403 chain_not_allowed - it means the corridor was never yours, so retrying will not help.Handle
429 by backing off, not by retrying immediately.Treat
no_route as a normal, temporary answer - liquidity moves.Keep the key on your server and out of anything a browser can read.