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:

  1. Initiate — you describe the source and destination. We return the orchestration with pay-in instructions (a crypto deposit address, or a bank account to pay into).
  2. Pay-in settles — the customer funds the source; it lands in your USD balance.
  3. 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.