Orchestrate
Status: Available
Anything to anything, with stablecoins between. One endpoint moves value from any source to any destination. You can pay in NGN and send out BTC. You can send in BTC and pay out EUR or KES. You can move any crypto to any fiat currency, or any crypto to a different crypto.
Every orchestration uses the same rail:
SOURCE → USDC (the stablecoin hub) → DESTINATION
The source converts to USDC, and USDC converts to whatever the destination needs. Stablecoins are the backbone — that hub is what makes any combination work with honest, 1:1-USD pricing.
Each side is either:
- fiat — a local currency, collected or paid out over local rails, or
- crypto — any supported asset on any supported network.
How it works (two phases)
Orchestration is asynchronous, because the pay-in settles before the pay-out can fire:
-
Initiate — you describe the
sourceanddestination. We return the orchestration with pay-in instructions (a crypto deposit address, or a bank account to pay into). - Pay-in settles — the customer funds the source; it lands in your USD balance.
-
Pay-out fires automatically — we send the destination leg from that
balance. Poll
GET /v1/orchestrate/{id}to follow it.
If the pay-out does not complete, the funds stay in your USD balance. You lose nothing. You can try again, or you can withdraw the funds.
Quote an orchestration
POST /v1/orchestrate/quote
curl {BASE_URL}/v1/orchestrate/quote \
-H "x-api-key: sk_test_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"source": { "kind": "fiat", "amount": 50000, "country": "NG", "currency": "NGN" },
"destination": { "kind": "crypto", "asset_id": "btc-bitcoin" }
}'
Response
{
"code": 200,
"message": "Quote created",
"data": {
"pay_in": { "amount": 50000, "unit": "NGN", "country": "NG" },
"usdc": 30.25,
"pay_out": { "amount": 0.00035, "unit": "btc-bitcoin" },
"fees": {
"in_fee": 0.5,
"out_fee": 0.4,
"total_fee": 0.9,
"currency": "USD"
},
"indicative": true
}
}
usdc is what lands at the hub after the inbound leg; pay_out is what the
recipient receives. Quotes are indicative — rates settle when the pay-in
lands.
Initiate an orchestration
POST /v1/orchestrate
Name the customer first
customer_id is required. Every rail that collects money asks who is
paying, and asks in more detail than a name — date of birth, address, an
identity document. That detail lives on a customer, so create the customer
once and reuse them:
curl {BASE_URL}/v1/customers \
-H "x-api-key: sk_test_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"type": "individual",
"first_name": "Ada", "last_name": "Obi",
"email": "ada@example.com",
"country": "NG", "phone": "+2348012345678",
"date_of_birth": "1996-04-12",
"address": "12 Marina Road, Lagos",
"id_number": "A01234567", "id_type": "passport"
}'
The response carries the id you pass as customer_id below. Customers you
have blocked cannot pay, and the orchestration is refused before any money
moves. See Customers.
Pay in NGN, send out BTC
curl {BASE_URL}/v1/orchestrate \
-H "x-api-key: sk_test_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_…",
"source": {
"kind": "fiat", "amount": 50000, "country": "NG", "currency": "NGN",
"customer_name": "Ada Obi"
},
"destination": {
"kind": "crypto", "asset_id": "btc-bitcoin",
"address": "bc1qrecipientaddress"
},
"reference": "order_8841"
}'
{
"code": 201,
"message": "Orchestration initiated",
"data": {
"id": "orch_…",
"status": "awaiting_payin",
"source": {
"kind": "fiat",
"amount": 50000,
"country": "NG",
"currency": "NGN"
},
"destination": {
"kind": "crypto",
"asset_id": "btc-bitcoin",
"address": "bc1q…"
},
"pay_in": {
"kind": "fiat",
"bank_info": {
"bank_name": "Providus Bank",
"account_name": "Figo / Ada Obi",
"account_number": "9900112233"
}
},
"reference": "order_8841",
"created_at": "2026-06-26T15:00:00.000Z"
}
}
Send in BTC, pay out KES
For a crypto source, give refund_to. This is an address your customer can
receive at, used only if the pay-in does not complete — not necessarily the one
they send from, since a payment made from an exchange leaves an address they
cannot be refunded at. The pay_in field of the response gives the deposit
address. It also gives a memo when the network needs one.
curl {BASE_URL}/v1/orchestrate \
-H "x-api-key: sk_test_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_…",
"source": {
"kind": "crypto", "amount": 0.01, "asset_id": "btc-bitcoin",
"refund_to": "bc1qcustomersendingaddress"
},
"destination": {
"kind": "fiat", "country": "KE", "currency": "KES", "method": "momo",
"recipient": { "account_name": "John Doe", "account_number": "+254712345678", "network": "MPESA" }
}
}'
{
"code": 201,
"message": "Orchestration initiated",
"data": {
"id": "orch_…",
"status": "awaiting_payin",
"pay_in": {
"kind": "crypto",
"address": "0xGeneratedDepositAddress",
"memo": null,
"asset_id": "btc-bitcoin"
}
}
}
Get an orchestration
GET /v1/orchestrate/{id}
Returns the orchestration with its current status and pay-in details.
List crypto assets
GET /v1/orchestrate/assets
The digital assets usable on either side, grouped by symbol with the networks
each is available on. Use a network's asset_id for a crypto source or
destination.
{
"code": 200,
"message": "Orchestration assets fetched successfully",
"data": [
{
"symbol": "USDC",
"asset_logo": "https://cdn.jsdelivr.net/…/USDC.png",
"preferred": true,
"chains": [
{
"asset_id": "usdc-polygon",
"chain": "pol",
"chain_name": "Polygon",
"chain_logo": "https://…/POL.png",
"decimals": 6,
"price": 0.9999,
"contract_address": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"direct": true,
"can_deposit": true,
"can_payout": true,
"checked_at": "2026-10-05T09:30:00.000Z"
},
{
"asset_id": "usdc-solana",
"chain": "sol",
"chain_name": "Solana",
"chain_logo": "https://…/SOL.png",
"decimals": 6,
"price": 0.9999,
"contract_address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"direct": false,
"can_deposit": true,
"can_payout": true,
"checked_at": "2026-10-05T09:30:00.000Z"
}
]
}
]
}
Same shape on all three assets listings. chain is a short code, not the
display name. preferred marks the assets we suggest first, and direct: true
means we send the asset as-is rather than bridging it.
can_deposit and can_payout say whether that corridor actually priced the
last time we asked — a listed asset is not the same as a priceable one. They
matter most here, because an orchestration needs both ends to work at once:
can_deposit on the source and can_payout on the destination.
Absent is not false. Both fields, and checked_at with them, are missing
when we have not probed that corridor yet. Filter on === false rather than
on falsiness, or you will hide everything we simply have not measured. See
payouts for the full field
notes.
Top-level fields
| Field | Type | Description |
|---|---|---|
customer_id |
string | Required. The customer paying. Create them with POST /v1/customers. |
source |
object | The inbound leg — see below. |
destination |
object | The outbound leg — see below. |
reference |
string | Your own reference, echoed back for reconciliation. |
The source object
| Field | Type | Description |
|---|---|---|
kind |
string | fiat or crypto. |
amount |
number | Fiat: local amount the customer pays. Crypto: amount of the asset sent. |
country |
string | Corridor country (fiat). |
currency |
string | Local currency (fiat). |
method |
string | bank or momo (fiat). Defaults to the corridor's standard. |
customer_name |
string | Paying customer (fiat bank). |
network, phone_number |
string | Mobile-money details (fiat momo). |
asset_id |
string | Asset the customer sends (crypto). |
refund_to |
string | An address the customer can receive at, used if the pay-in fails (crypto). Not necessarily where they send from. |
The destination object
| Field | Type | Description |
|---|---|---|
kind |
string | fiat or crypto. |
country |
string | Corridor country (fiat). |
currency |
string | Local currency the recipient receives (fiat). |
method |
string | bank or momo (fiat). |
recipient |
object | Bank / mobile-money recipient (fiat). |
asset_id |
string | Asset to send (crypto). |
address |
string | Destination wallet address (crypto). |
Status values
| Status | Meaning |
|---|---|
awaiting_payin |
Waiting for the source leg to settle into your balance. |
paying_out |
Pay-in settled; the destination leg is in flight. |
completed |
Destination delivered. |
failed |
The destination leg failed; funds are held safely in your USD balance. |