Trading
Trading on the Partner API is a two-step flow — calculate a quote to lock a price, then execute it to place the order. This page explains asset pairs, the quote token model, buy/sell sides, and how trading interacts with client balances.
Trading lets your clients buy and sell crypto at a price locked in real time. The Partner API uses a two-step model — first calculate a quote, then execute it — so your clients always trade at a guaranteed price with no slippage.
Overview
Trading on behalf of a client is always two steps:
- Calculate a quote — Call
POST /v1/partner/quoteswith the client, asset pair, side, and amount. CoinMENA returns a locked market price, fee breakdown, and a short-lived JWTtoken. - Execute the quote — Call
POST /v1/partner/quotes/executewith thetokenfrom step 1. The order is placed at the locked price and returned as an order object.
The token is the bridge between the two steps. It is single-use and time-limited — if it expires or is reused, execution fails with error 4008.
Why two steps?The two-step model lets you show the client the exact price, fees, and totals before they commit. They see the final numbers, confirm, and only then do you execute. This avoids slippage and gives the client full visibility into what they are about to pay or receive.
The Core Rule
Quote → Execute — the rule you must not breakA quote must be executed within its
lifetimeusing thetokenreturned by calculation. If the quote expires, execution will fail with4008and a new quote must be requested. Tokens are single-use — once executed, they cannot be reused.
Price Lock & Slippage
Quotes lock the price for the duration specified in the lifetime field (in seconds). Inside that window, the price returned in the quote is exactly what the client pays or receives — there is no slippage.
After lifetime expires, the market price may have moved. Execution with the expired token is rejected, and you must re-quote to get the current price.
End-to-End Trading Flow
1. List pairs 2. Calculate quote 3. Execute quote
GET asset-pairs POST quotes POST quotes/execute
→ price, limits → locked price + token → order created
(valid for `lifetime` sec)
A typical trade end-to-end:
- Discover pairs — Call
GET /v1/partner/asset-pairsto list tradeable pairs and their current buy/sell prices. - Calculate a quote — Submit the client, pair, side, and amount. Receive a locked price, fees, and a
tokenvalid forlifetimeseconds. - Show the client the final numbers — Price, fees, total. Let the client confirm.
- Execute — Send the
tokenback before it expires. Optionally include apartner_order_reffor reconciliation. - Receive the order — The API returns the created order object. Persist the order ID (and your
partner_order_refif you used one).
The resulting order is covered in the Orders guide.
Execution is synchronousA successful execute response means the order has been placed and returned in its final state. Check the
statusfield on the returned order to confirm the outcome — typicallycompletedon success, orfailed/rejectedif something went wrong during placement. You do not need to poll for completion.
Asset Pairs
An asset pair is a tradeable combination of a base asset and a quote asset, written in BASE-QUOTE format — e.g. BTC-USD.
Reading the Pair Format
asset_pair: BTC-USD
│ │
│ └── quote asset (USD) — the pricing/fiat asset
└────── base asset (BTC) — the asset being bought or sold
| Term | Meaning |
|---|---|
| Base asset | The asset being bought or sold. For BTC-USD, the base is BTC |
| Quote asset | The asset used to price the trade. For BTC-USD, the quote is USD |
buy price | Price per unit of base asset when the client buys |
sell price | Price per unit of base asset when the client sells |
tradable | Boolean — whether the pair is currently available for trading |
Asset Pair Object
Fetched via GET /v1/partner/asset-pairs or GET /v1/partner/asset-pairs/{asset_pair_id}:
| Field | Type | Description |
|---|---|---|
id | string | Pair identifier in BASE-QUOTE format (e.g. BTC-USD) |
type | string | Pair type (e.g. crypto) |
base_id | string | Base asset identifier |
quote_id | string | Quote asset identifier |
decimal_places | integer | Decimal precision for quote asset amounts |
inv_decimal_places | integer | Decimal precision for base asset amounts |
min_volume | string | Minimum tradeable volume in base asset units |
max_volume | string, nullable | Maximum tradeable volume in base asset units. null if no upper limit |
trading_fee | string | Trading fee percentage applied to this pair |
tradable | boolean | Whether this pair is currently available for trading |
buy | string | Current buy (ask) price in quote asset |
sell | string | Current sell (bid) price in quote asset |
daily_change_pct | string | 24-hour price change percentage |
Example
{
"id": "BTC-USD",
"type": "crypto",
"base_id": "BTC",
"quote_id": "USD",
"decimal_places": 2,
"inv_decimal_places": 8,
"min_volume": "0.0001",
"max_volume": "100",
"trading_fee": "0.5",
"tradable": true,
"buy": "67550.75",
"sell": "67450.25",
"daily_change_pct": "2.35"
}
Checktradablebefore calculating a quoteIf
tradableisfalse, quote calculation returns error4000. Always check the flag when surfacing pairs to your users so they do not attempt trades that will be rejected.
Precision Per Pair
Every pair carries its own decimal precision:
inv_decimal_places— precision for base asset amountsdecimal_places— precision for quote asset amounts
For the BTC-USD example above:
base_amountis accepted up to 8 decimal places (e.g.0.00100000)quote_amountis accepted up to 2 decimal places (e.g.67.50)
These values vary per pair — always read them from the pair object rather than hardcoding.
Trading Limits
Every pair has min_volume and max_volume (nullable) in base asset units. Quotes outside these bounds are rejected:
| Error Code | Trigger |
|---|---|
4004 | Requested amount is below the pair's min_volume |
4003 | Requested amount is above the pair's max_volume |
For 4003 and 4004, the error response includes both the applicable limit and a quote object in msg_detail — useful for showing the client the current rate and why their trade was rejected. See Error Codes.
Buy vs Sell
The side field controls the direction of the trade. Both sides use the same endpoints and schemas.
| Side | Plain-English Meaning | Client Spends | Client Receives |
|---|---|---|---|
buy | Client is buying the base asset using the quote asset (e.g. buy BTC with USD) | Quote asset (USD) | Base asset (BTC) |
sell | Client is selling the base asset for the quote asset (e.g. sell BTC for USD) | Base asset (BTC) | Quote asset (USD) |
If the client does not have enough balance in the asset they need to spend, quote calculation returns error 3005 — Insufficient funds. The error response includes a quote object in msg_detail with the calculated price, so you can still show the client the market rate.
Funding before tradingFor
buyorders, the client needs a quote asset (fiat) balance — fund them first viaPOST /v1/partner/deposits. Forsellorders, the client needs a base asset (crypto) balance. See the Funding guide.
Calculating a Quote
A quote locks a price for the trade the client is about to make. Submit to POST /v1/partner/quotes:
| Field | Required | Description |
|---|---|---|
partner_client_id | Yes | The client on whose behalf the quote is calculated |
asset_pair | Yes | The pair identifier (e.g. BTC-USD) |
side | Yes | buy to purchase the base asset, sell to sell the base asset |
base_amount | Conditional | Amount in the base asset (e.g. "0.001" for BTC). Provide either this or quote_amount |
quote_amount | Conditional | Amount in the quote asset (e.g. "100.00" for USD). Provide either this or base_amount |
base_amountorquote_amount— pick oneProvide exactly one of the two. Sending both, or neither, returns a validation error. Use
base_amountwhen the client wants to buy/sell a specific quantity of the crypto; usequote_amountwhen the client wants to spend/receive a specific amount of fiat.
Request Example
{
"partner_client_id": "user_12345",
"asset_pair": "BTC-USD",
"side": "buy",
"base_amount": "0.001"
}Response — The Quote Object
| Field | Type | Description |
|---|---|---|
market_price | string (decimal) | The locked price for this quote, with partner spread applied |
base_amount | string (decimal) | Trade amount in base asset |
quote_amount | string (decimal) | Trade amount in quote asset |
fee_amount | string (decimal) | Total fee charged for this trade |
vat_amount | string (decimal) | VAT amount applied to the fee |
vat_percentage | string (decimal) | VAT rate percentage applied |
lifetime | integer | Number of seconds this quote remains valid |
token | string | JWT token required to execute this quote. Pass to the execute endpoint |
Example
{
"result": {
"market_price": "67500.50",
"base_amount": "0.00100000",
"quote_amount": "67.50",
"fee_amount": "0.34",
"vat_amount": "0.00",
"vat_percentage": null,
"lifetime": 15,
"token": "eyJhbGciOiJIUzI1NiJ9..."
}
}The Quote Token
| Property | Behavior |
|---|---|
| Format | JWT string |
| Lifetime | The lifetime field (in seconds) tells you how long the token is valid |
| Single-use | Once a token is executed successfully, it cannot be reused |
| Client-bound | The token is bound to the partner_client_id that calculated the quote — you cannot execute it for a different client |
| On expiry | Using an expired token returns error 4008 — Trading token is invalid |
| On reuse | Using an already-executed token returns error 4008 |
Design your UI around the token lifetimeQuote lifetimes are short (typically a few seconds). If you show the client a confirmation screen, either:
- Auto-refresh the quote in the background as the client reads, or
- Submit the execute call immediately upon confirmation and handle any
4008by re-quoting and retrying.
Executing a Quote
Once you have a valid token, execute it to place the order via POST /v1/partner/quotes/execute.
Request
| Field | Required | Description |
|---|---|---|
partner_client_id | Yes | Must match the client used when calculating the quote |
token | Yes | The JWT token from the calculate response |
partner_order_ref | Optional | Your own internal reference for the resulting order. Must be unique per partner. Use it to retrieve the order later via GET /v1/partner/orders/ref/{partner_order_ref} |
Request Example
{
"partner_client_id": "user_12345",
"token": "eyJhbGciOiJIUzI1NiJ9...",
"partner_order_ref": "order-abc-123"
}Response
The execute response returns the created order object — see the Orders guide for the full order schema and status semantics.
partner_order_ref Uniqueness
partner_order_ref Uniqueness
partner_order_refmust be unique per partnerIf you supply a
partner_order_ref, the value must be unique across all orders under your partner account. Reusing a value returns error2020with HTTP409 Conflict. This is a reconciliation-uniqueness rule, not a duplicate-submission guard — always use a fresh, unique reference for every new order.
Usepartner_order_reffor reconciliationIf you pass a
partner_order_ref, you can later fetch the order using your own reference rather than CoinMENA's internal order ID. This simplifies reconciliation when your system is the system of record. See Orders for retrieval patterns.
Why Execute Can Fail
Beyond transport-level errors (timeouts, 5xx, etc.), execute can fail for these business reasons:
| Reason | Error Code | Fix |
|---|---|---|
| Token is expired, already used, or client-mismatched | 4008 | Re-calculate a fresh quote and use the new token |
Duplicate partner_order_ref | 2020 | Use a new unique reference, or check whether the original order already succeeded |
| Client is not verified | 2009 | Only verified clients can trade |
| Client is deactivated | 2010 | Activate the client via PUT /v1/partner/clients/{partner_client_id}/activation |
| Invalid client ID | 2008 | Verify partner_client_id matches the client from the quote step |
Failures specific to the quote step (4000, 4001, 4003, 4004, 3005, 2013) cannot occur at execute — those are already resolved by the time you have a valid token. If quote succeeded, the trade is priced, within limits, and the client has funds.
Client Trading Eligibility
Before any trade can go through, the client must meet these conditions:
| Requirement | Check | Error If Not Met |
|---|---|---|
| Client exists | partner_client_id belongs to your partner account | 2008 |
| Client is verified | status: verified | 2009 |
| Client is active | active: true | 2010 |
| Sufficient balance | For buy: quote asset balance ≥ total cost. For sell: base asset balance ≥ amount | 3005 |
| Pair is tradeable | tradable: true on the asset pair | 4000 |
| Trading is not paused | Price feed is available | 4001 |
| Amount within limits | min_volume ≤ base_amount ≤ max_volume | 4003 / 4004 |
See Clients for the active and status model, and Error Codes for full error details.
Amount Fields — Decimals and Precision
Amount fields (base_amount, quote_amount, market_price, fee_amount, etc.) are returned as JSON strings and may use scientific notation for very small values (e.g. "0E-8", "1e-8").
Do not parse decimals as numbersParsing amount fields as JavaScript
numberor Pythonfloatcauses precision loss that compounds across trades. Always use a decimal-aware type —Decimalin Python,decimal.jsin JavaScript, or equivalent.
Submit amounts as strings in your requests too — e.g. "base_amount": "0.001", not 0.001. This preserves precision on the way in.
Match the pair's inv_decimal_places and decimal_places when submitting amounts — see Precision Per Pair.
Common Mistakes
The most frequent issues partners hit when implementing trading:
| Mistake | Fix |
|---|---|
Sending both base_amount and quote_amount | Send exactly one. Both or neither returns a validation error |
Parsing amounts as float / Number | Use a decimal-aware type — Decimal in Python, decimal.js in JavaScript |
Using a token after lifetime seconds | Re-calculate a fresh quote — tokens are short-lived |
| Re-using a token after a successful execute | Tokens are single-use — each execute requires a new quote |
Executing with a different partner_client_id than the quote | The token is bound to the original client — the call returns 4008 |
Calculating a quote on a tradable: false pair | Returns 4000. Filter pairs by tradable: true before showing them to users |
| Showing an expired quote price in the UI | Refresh or re-quote before the lifetime elapses |
| Assuming buy and sell prices are the same | buy and sell differ by the partner spread — show the correct side's price based on the client's action |
Reusing a partner_order_ref across orders | Each reference must be unique per partner. Duplicates return 2020 |
Related Error Codes
The errors you are most likely to see during trading:
| Code | Trigger | Endpoint |
|---|---|---|
2008 | Invalid client ID | Quotes, Execute |
2009 | Client is not yet verified | Quotes, Execute |
2010 | Client is deactivated | Quotes, Execute |
2013 | Invalid asset pair | Quotes |
2020 | Duplicate partner_order_ref | Execute |
3005 | Insufficient funds | Quotes |
4000 | Trading is not available (pair is not tradeable) | Quotes |
4001 | Trading is temporarily unavailable (price feed down) | Quotes |
4003 | Amount exceeds the maximum allowed | Quotes |
4004 | Amount is below the minimum allowed | Quotes |
4008 | Trading token is invalid (expired, reused, or client mismatch) | Execute |
See the Error Codes guide for the full reference.
Updated 1 day ago
| Next Step | Description |
|---|---|
| Orders | The order object, statuses, and retrieval patterns |
| Funding | Deposit and withdraw fiat balances for your clients |
| Error Codes | Full reference for every error the API returns |
