Your First Trade
Relevant if you use Exchange to convert currencies, hold balances or withdraw to your own accounts. If you only collect payments or send payouts, you don’t need this subsection. See Which parts you need.
This is the one Exchange test to run before any other. It converts USD to NGN and checks the filled order in your balances, which proves four things at once: your credentials work for Exchange, you can reach the API, your quote request is accepted, and you can read the result back in your balances.
The example sells USD 1,000.00 for NGN. Substitute any pair from Currencies & Conventions; the steps do not change.
This page is a test procedure. For how quotes and orders behave, see Trading.
Before you start
You’ll need your sandbox client_id and client_secret. Exchange doesn’t use a merchantId.
Your sandbox balances start at zero. Ask your account manager to top up your sandbox organisation in the currency you want to sell — USD for this example.
Step 1 — Get an access token
POST /v1/auth/oauth2/token — API reference
Pass: a 200 with an access_token. Tokens last 24 hours and there is no refresh token — request a new one when it expires.
If this fails: a 401 means the client_id or client_secret is wrong — check you’re using the sandbox set, not production. A 400 usually means the body was form-encoded; it must be JSON. See Authentication.
Step 2 — Check your balance
GET /v1/balances — API reference
Pass: a 200 with an entry for both USD and NGN, including a zero NGN balance, and at least USD 1,000.00 available. Filtering on currency keeps both entries on the first page — without it the list is paginated, and you’d follow nextCursor to find them.
If USD is 0.00, your sandbox hasn’t been funded yet — see Before you start. Note the balances down; you’ll compare against them in Step 5.
Step 3 — Request a quote
POST /v1/quotes — API reference
Pass: a 201 with a quoteId, a status of active, and both legs filled in. Keep the quoteId and note the expiresAt — the quote lasts seconds, not minutes, so go straight to the next step.
A few things worth getting right the first time:
- Amounts are decimal strings, not minor units.
"1000.00"is USD 1,000.00. Sending a number rather than a string is a common first-run mistake. sidesays which legamountfixes.sellfixes what you give up;buyfixes what you receive. The response fills in the other leg either way.- Quoting doesn’t touch your balance. Balances are checked at acceptance, so you can request as many quotes as you like.
If this fails: a 404 with RATE_NOT_AVAILABLE means no rate is available for that pair right now. A 400 means a field is missing or malformed; the error.message names it. If the response tells you to use comparative quotes, your account is enrolled in more than one execution option — request comparative quotes and accept the one marked "executable": true.
Step 4 — Accept the quote
POST /v1/quotes/{quoteId}/accept — API reference
Pass: a 201 with an orderId, a status of filled, and sellAmount, buyAmount and rate exactly as quoted. The trade is done and your balances have already moved, so there’s nothing to poll.
If this fails: a 409 with QUOTE_EXPIRED means you were too slow — request a new quote and accept it straight away. A 422 with INSUFFICIENT_BALANCE means your available USD doesn’t cover sellAmount; error.details shows what’s available and what’s required.
Step 5 — Confirm your balances moved
GET /v1/balances — API reference
Repeat the call from Step 2, with the same currency filter:
Pass: USD has gone down by sellAmount and NGN has gone up by buyAmount.
For the audit trail, Get balance statement at GET /v1/balances/statements?currency=USD now shows an order_sell entry whose relatedId is your orderId, and the NGN statement shows the matching order_buy. The statement is the source of truth for reconciliation, so it’s worth checking your code can read it now.
What you have just proved
Then try the failures
Exchange has no trigger values in the sandbox. You produce each failure by setting up the state that causes it:
Branch on error.code, never on error.message. The message is for people and can change.
Only CrissCross can cancel an order, so the sandbox can’t produce a cancelled one. Handle order.cancelled anyway: your balances are reverted when it fires.
Next
- Set up a webhook endpoint in your sandbox dashboard, subscribe it to
order.filled, and re-run this test. See Testing Webhooks for how to inspect and resend deliveries. - If you move funds out to your own bank account or wallet, run Your First Withdrawal.