Skip to main content
The express quote collapses the pricing step. Instead of creating an intent and then fetching the quote it produced, you send one call and get a priced, allocated quote straight back. Everything after pricing is unchanged — you still accept, authorize and complete.

What you’ll need

Express quote lives on the exchange base URL (/exchange/api/v1), but accept, authorize, status and complete are on /partners/marketplace/quote/{id}/.... This guide shows the full path on every call — do not assume one prefix for the whole flow.

Express vs. the intent flow

1

Intent flow — two calls to price

POST /partners/marketplace/intent returns a quote with a ttl, and you can inspect or negotiate before committing.
2

Express — one call to price

POST /partners/marketplace/express/quote returns the same priced quote directly.
3

From here they are identical

Accept, authorize, poll, complete — the same four calls on the same paths.
The real difference is the ttl. The intent flow hands you a quote with a countdown you can show a customer before they commit. Express gives you no ttl until you accept — so express suits a flow where you price and commit in one motion, and the intent flow suits one where a human is deciding.

Step 1: Create the express quote

Keep the quoteId.
amount means the source amount here, despite what you may read elsewhere. Sending amount: 5 with amountDirection: "source" produced a trade of 5 GHS in and 578 NGN out. Some reference material describes amount as the destination amount — it is not, when amountDirection is source. Set amountDirection explicitly and check totalSourceAmount against what you expect before accepting.
amount (5) and totalSourceAmount (5.17301038) differ by the fee of 0.17301038 — the buyer pays slightly more than the headline amount. The separate transactionFee of 20.00 NGN is charged on the destination side. Two fees, two currencies.
A payment block is accepted here but does nothing. You can pass the buyer’s mobile money details to this call and it will return 200 — but the response has no metadata.mobileMoneyPayIn and the status is PENDING, not AWAITING_PAYMENT. No phone is charged. Express prices the trade; it does not start the payment. You still need Step 2.
externalReference is not an idempotency key at this step. Sending the same reference twice produced two different quotes with two different quoteIds, both 200. Retrying a timed-out express quote can leave you with duplicate quotes. Track the quoteId yourself and cancel any you abandon.
The pair must have real liquidity. Asking for more than a provider holds returns 400 "Unable to fulfill requested volume at the moment." — a different error from "No providers available", which means nobody trades the pair at all. See Find a Liquidity Provider.

Step 2: Accept the quote and charge the buyer

From here the flow is identical to the intent flow. Note the path drops express:
Do not leave express in the accept path. Calling /partners/marketplace/express/quote/{id}/accept returns 401 with a completely empty body — no JSON, no error code. It looks like an auth failure on a perfectly good key. The accept path has no express segment.
Now the quote is AWAITING_PAYMENT, mobileMoneyPayIn has appeared, and ttl is 3599 — the countdown starts here, not at pricing.
Mobile money on dev is GHS only, and provider codes are lowercasemtn, airtel, telecel.

Step 3: Authorize with the OTP

The full quote comes back. What changes is the message:
The code was accepted. The money has not arrived yet.

Step 4: Poll until the payment settles

Watch metadata.mobileMoneyPayIn.status, not the top-level status. The payment reads successful while the quote is still AWAITING_PAYMENT — that is correct, not a stuck trade. The top-level status only changes when you complete.
In test mode this settled within about four seconds. Poll every few seconds rather than in a tight loop, and give up against ttl rather than a fixed number of tries.

Step 5: Complete the trade

status is SUCCESSFUL and ttl is 0. Done.

Confirming the money moved

Reading the NGN balance before and after:
Exactly totalDestinationAmount.
The source balance does not move. GHS was unchanged across the trade. The buyer paid by mobile money, so the source side never touches your GHS balance — only the destination is credited. Do not reconcile by expecting a matching debit.
Completing twice is safe. Calling complete a second time returned 200 with SUCCESSFUL again and the balance stayed at 5000127232.75 — no double credit. A retry after a network timeout is a safe no-op.
The provider’s liquidity drops by what you took. The NGN/GHS listing went from 768.27 to 190.27 — the 578.00 this trade consumed. That is the clearest independent confirmation the trade really settled.

When things go wrong

What to do next