Place an OTC order

An OTC order is a currency exchange that needs approval before it executes. Unlike a spot trade, which executes the moment you submit it, an order is created in a pending_approval state and a second user approves or rejects it before any funds move. Use orders for larger or controlled exchanges that need a second pair of eyes.

📘

Order vs spot trade

Use a spot trade for an instant conversion at the current rate. Use an order when the exchange needs approval first, when you want to set a target rate (a limit order), or when the proceeds settle to a bank account or wallet you've saved as a settlement instruction.

Order types

  • Market order: executes at the current rate once approved.
  • Limit order: waits until the rate reaches your target_rate, then executes once approved.

Create an order

Use the Create order endpoint. Each order requires the following fields:

FieldTypeDescription
order_typestringmarket or limit
pairobjectthe currency pair, as { "base": "USD", "quote": "NGN" }
amountstringamount of the base currency to exchange, greater than zero
target_ratestringrequired for limit orders; the rate at which the order should execute
sidestringoptional, buy or sell. Defaults to sell
source_typestringoptional, account or wallet: where the funds come from
source_idstringoptional, the account or wallet ID
destination_typestringoptional, account, wallet, or ssi: where the proceeds go
destination_idstringoptional, the account, wallet, or settlement instruction ID

Use Get all currency pair rates to confirm a pair is supported before you create an order.

Market order

curl --request POST \
     --url https://api.gravv.xyz/v1/fx/orders \
     --header 'Api-Key: <Api Key>' \
     --header 'Idempotency-Key: 979879887678789_attempt_1' \
     --header 'content-type: application/json' \
     --data '
{
  "order_type": "market",
  "pair": { "base": "USD", "quote": "NGN" },
  "amount": "1000",
  "source_type": "account",
  "source_id": "5d9e677f-f071-4881-a861-7cdcebacd9d5",
  "destination_type": "ssi",
  "destination_id": "a14ec356-f33d-4384-ba68-4d9bfa19765b"
}
'

The order is created in pending_approval and returns its order_id:

{
  "data": {
    "id": "8f1d2c0e-2b7a-4f3e-9c1a-7d2b6e4f9a01",
    "order_id": "OTC-1737031200-AB12",
    "order_type": "market",
    "side": "sell",
    "pair": { "base": "USD", "quote": "NGN" },
    "amount": "1000",
    "amount_currency": "USD",
    "converted_amount": "1550000",
    "converted_currency": "NGN",
    "rate": "1550",
    "status": "pending_approval",
    "destination_type": "ssi",
    "destination_id": "a14ec356-f33d-4384-ba68-4d9bfa19765b",
    "created_at": "2026-06-20T10:29:16.000Z",
    "expires_at": "2026-06-20T10:44:16.000Z"
  },
  "error": null
}

Limit order

A limit order adds a target_rate. It executes once the rate reaches that value, after approval:

curl --request POST \
     --url https://api.gravv.xyz/v1/fx/orders \
     --header 'Api-Key: <Api Key>' \
     --header 'Idempotency-Key: 979879887678789_attempt_2' \
     --header 'content-type: application/json' \
     --data '
{
  "order_type": "limit",
  "pair": { "base": "USD", "quote": "NGN" },
  "amount": "1000",
  "target_rate": "1600",
  "source_type": "account",
  "source_id": "5d9e677f-f071-4881-a861-7cdcebacd9d5",
  "destination_type": "account",
  "destination_id": "c2f9f2d0-1234-4abc-9def-0123456789ab"
}
'

Where proceeds settle

Set destination_type to control where the converted funds go:

  • account: an internal account, by destination_id.
  • wallet: a crypto wallet, by destination_id.
  • ssi: a saved settlement instruction, by its ID. Use this to settle to an external bank account or wallet you've saved ahead of time.

Order status

An order moves through these statuses:

StatusDescription
pending_approvalcreated and waiting for a second user to approve or reject
activeapproved; the funds are charged and the order is executing
completedexecuted and settled
rejecteda reviewer rejected the order
cancelledcancelled before execution
expirednot approved before it expired

Track an order

Check a single order with Get order using its order_id:

curl --request GET \
     --url https://api.gravv.xyz/v1/fx/orders/OTC-1737031200-AB12 \
     --header 'Api-Key: <Api Key>'

List your orders with List orders. You can filter with status, order_type, side, search, start_date, and end_date, and page with page and items_per_page (default 20, max 100):

curl --request GET \
     --url 'https://api.gravv.xyz/v1/fx/orders?status=pending_approval&items_per_page=20' \
     --header 'Api-Key: <Api Key>'

Cancel an order

Cancel an order that hasn't executed with Cancel order:

curl --request POST \
     --url https://api.gravv.xyz/v1/fx/orders/OTC-1737031200-AB12/cancel \
     --header 'Api-Key: <Api Key>'

Next step

A new order sits in pending_approval until someone approves it. See Approve or reject OTC orders.


Did this page help you?