Trading
Buy and sell crypto for a user—request a quote, execute it as an order, and track the order by id or in a list.
Buy and sell crypto on a user's behalf in two steps: request a quote for a pair and side, then place an order that executes that quote. Read any order back by its id, or list a wallet's orders to track them.
Authentication: every endpoint on this page accepts your Owner API Key (x-api-key) or a user token (Authorization: Bearer). The wallet in the path must belong to your customer and, with a user token, to that user. Any other wallet answers 404 WALLET_NOT_FOUND, so ids can't be probed across accounts.
Authorization: Bearer YOUR_USER_TOKENEvery response uses the standard envelope and error shape. Amounts are decimal strings and pairs are always BASE/QUOTE. Both POST endpoints honor x-idempotency-key.
Lifecycle
A quote locks a price for a short window. An order executes exactly one quote.
| Object | Status | Meaning |
|---|---|---|
| Quote | OPEN | Priced and executable until expires_at. |
| Quote | EXECUTED | An order already used it. It can't be executed again. |
| Quote | EXPIRED | expires_at passed before an order used it. |
| Order | COMPLETED | The trade settled. completed_at is set. |
| Order | PENDING / PROCESSING | Accepted but not settled yet. |
| Order | FAILED | The trade didn't execute. status_reason says why. |
Placing an order moves its quote to EXECUTED, even when the order ends FAILED. To retry, request a new quote. Most orders settle on execution and come back COMPLETED. An order keeps the status reported when it executed.
Request Quote
Price a trade for a pair and side. Send exactly one of quantity (in the base currency) or amount (in the quote currency). Pass the returned id to Place Order before expires_at. Quotes live for seconds, not minutes, so treat expires_at as the authority.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| wallet_id | string (UUID) | Yes | Wallet that trades. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| pair | string | Yes | Pair as BASE/QUOTE, e.g. BTC/USD. The compact form (btcusd) is deprecated. |
| side | string | Yes | BUY or SELL. Lowercase is deprecated. |
| quantity | string | One of | Base-currency quantity as a positive decimal string, e.g. "0.50000000". |
| amount | string | One of | Quote-currency amount as a positive decimal string, e.g. "1000.00". |
Send exactly one of quantity or amount; both or neither returns 422 pointing at /amount. JSON numbers are still accepted for quantity and amount but are deprecated.
The quote object
| Field | Type | Description |
|---|---|---|
| id | string (UUID) | Quote id. Pass it as quote_id when you place the order. |
| quote_id | string (UUID) | Same value as id. |
| object | string | Always quote. |
| status | string | OPEN, EXECUTED, or EXPIRED. |
| wallet_id | string (UUID) | Wallet the quote belongs to. |
| user_id | string (UUID) | Owner of the wallet. |
| pair | string | BASE/QUOTE, e.g. BTC/USD. |
| base_currency | string | Base asset, e.g. BTC. |
| quote_currency | string | Quote asset, e.g. USD. |
| side | string | BUY or SELL. |
| quantity | string | Quantity in the base currency, at its scale. |
| amount | string | Amount in the quote currency, at its scale. |
| buy_price | string | null | Price in the quote currency. Expect it on BUY quotes; read whichever price isn't null. |
| sell_price | string | null | Price in the quote currency. Expect it on SELL quotes. |
| fee | string | null | Fee in the quote currency. |
| idempotency_key | string | null | The x-idempotency-key you sent. |
| created_at | string | When the quote was priced (UTC). |
| expires_at | string | When the quote stops being executable (UTC). |
{
"pair": "BTC/USD",
"side": "BUY",
"quantity": "0.50000000"
}Success Response (201 Created):
{
"success": true,
"data": {
"id": "be27cd3f-3276-523e-a48c-f0661a079e72",
"quote_id": "be27cd3f-3276-523e-a48c-f0661a079e72",
"object": "quote",
"status": "OPEN",
"wallet_id": "90f904da-dc14-535e-b2db-56be50db02c8",
"user_id": "43ac9e63-96c4-5f50-b788-dfbc6b265375",
"pair": "BTC/USD",
"base_currency": "BTC",
"quote_currency": "USD",
"side": "BUY",
"quantity": "0.50000000",
"amount": "20000.0000",
"buy_price": "40000.0000",
"sell_price": null,
"fee": "100.0000",
"idempotency_key": "6f1c2a7e-9b3d-4f5a-8c2e-1d7b9a0e4c33",
"created_at": "2026-10-12T14:30:00Z",
"expires_at": "2026-10-12T14:30:15Z"
},
"meta": {
"timestamp": "2026-10-12T14:30:00Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}Validation error (422 Unprocessable Entity), sending both quantity and amount:
{
"success": false,
"errors": [
{
"code": "VALIDATION_ERROR",
"message": "Provide exactly one of 'quantity' or 'amount'.",
"source": { "pointer": "/amount" }
}
],
"meta": {
"timestamp": "2026-10-12T14:30:00Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/{{walletID}}/trading/quotes" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_USER_TOKEN" \
-H "x-idempotency-key: 6f1c2a7e-9b3d-4f5a-8c2e-1d7b9a0e4c33" \
-d '{
"pair": "BTC/USD",
"side": "BUY",
"quantity": "0.50000000"
}'Errors: 400 the quote can't be priced (BAD_REQUEST) · 401 UNAUTHORIZED · 404 WALLET_NOT_FOUND · 422 VALIDATION_ERROR (source.pointer names the field) · 429 RATE_LIMIT_EXCEEDED · 502 BAD_GATEWAY, pricing unavailable.
Place Order
Execute an OPEN quote. Pass its id as quote_id; the order takes the pair and side from the quote, so you don't repeat them. The response carries the order and a Location header pointing at Get Order.
A quote rejected at execution still returns 201, with status: "FAILED" and status_reason.code: "QUOTE_REJECTED". That quote counts as executed: request a new one to retry.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| wallet_id | string (UUID) | Yes | Wallet the quote was requested for. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| quote_id | string (UUID) | Yes | id from Request Quote. |
| quantity | string | No | Overrides the quoted base-currency quantity. Positive decimal string. |
| amount | string | No | Overrides the quoted quote-currency amount. Positive decimal string. |
Send at most one of quantity or amount; both returns 422 pointing at /amount. Most orders send quote_id only. JSON numbers are still accepted for the overrides but are deprecated.
The order object
| Field | Type | Description |
|---|---|---|
| id | string (UUID) | Order id. |
| order_id | string (UUID) | Same value as id. Only on the Place Order response; Get Order and List Orders return id only. |
| object | string | Always order. |
| status | string | PENDING, PROCESSING, COMPLETED, or FAILED. |
| status_reason | object | null | { code, message } when the order failed, e.g. QUOTE_REJECTED. See statuses. |
| quote_id | string (UUID) | Quote the order executed. |
| transaction_id | string (UUID) | null | Ledger transaction for the trade in the wallet's history. null for a failed order. |
| user_id | string (UUID) | Owner of the wallet. |
| wallet_id | string (UUID) | Wallet that traded. |
| customer_id | string | null | Your customer id. |
| pair | string | BASE/QUOTE, e.g. BTC/USD. |
| base_currency | string | Base asset. |
| quote_currency | string | Quote asset. |
| side | string | BUY or SELL. |
| quantity | string | null | Executed quantity in the base currency. |
| price | string | null | Executed price in the quote currency. |
| amount | string | null | Executed amount in the quote currency. |
| fee | string | null | Fee in the quote currency. |
| tx_hash | null | Always null: trades settle off-chain. |
| network | null | Always null: trades settle off-chain. |
| idempotency_key | string | null | The x-idempotency-key you sent. |
| created_at | string | Creation time (UTC). |
| updated_at | string | Last change (UTC). |
| completed_at | string | null | When the order reached COMPLETED or FAILED (UTC). |
| created_by | string | null | Actor that created the order. |
| updated_by | string | null | Actor that last changed the order. |
{
"quote_id": "be27cd3f-3276-523e-a48c-f0661a079e72"
}Success Response (201 Created):
{
"success": true,
"data": {
"id": "168fc813-35b8-5447-89ed-07ec366f33c6",
"order_id": "168fc813-35b8-5447-89ed-07ec366f33c6",
"object": "order",
"status": "COMPLETED",
"status_reason": null,
"quote_id": "be27cd3f-3276-523e-a48c-f0661a079e72",
"transaction_id": "be2fa389-4b35-5c80-a830-2e849e9df31d",
"user_id": "43ac9e63-96c4-5f50-b788-dfbc6b265375",
"wallet_id": "90f904da-dc14-535e-b2db-56be50db02c8",
"customer_id": "corp-redundant-firewall",
"pair": "BTC/USD",
"base_currency": "BTC",
"quote_currency": "USD",
"side": "BUY",
"quantity": "0.50000000",
"price": "40000.0000",
"amount": "20000.0000",
"fee": "100.0000",
"tx_hash": null,
"network": null,
"idempotency_key": "9a7c3e1f-5b2d-4c8e-a6f0-1d3b5e7f9a2c",
"created_at": "2026-10-12T14:30:04Z",
"updated_at": "2026-10-12T14:30:05Z",
"completed_at": "2026-10-12T14:30:05Z",
"created_by": "43ac9e63-96c4-5f50-b788-dfbc6b265375",
"updated_by": "43ac9e63-96c4-5f50-b788-dfbc6b265375"
},
"meta": {
"timestamp": "2026-10-12T14:30:05Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}Quote rejected at execution (201 Created):
{
"success": true,
"data": {
"id": "5d0c7a91-2f4b-4e6a-9c13-8b7e2f1a0d44",
"order_id": "5d0c7a91-2f4b-4e6a-9c13-8b7e2f1a0d44",
"object": "order",
"status": "FAILED",
"status_reason": {
"code": "QUOTE_REJECTED",
"message": "The quote was rejected at execution. Request a new quote."
},
"quote_id": "be27cd3f-3276-523e-a48c-f0661a079e72",
"transaction_id": null,
"user_id": "43ac9e63-96c4-5f50-b788-dfbc6b265375",
"wallet_id": "90f904da-dc14-535e-b2db-56be50db02c8",
"customer_id": "corp-redundant-firewall",
"pair": "BTC/USD",
"base_currency": "BTC",
"quote_currency": "USD",
"side": "BUY",
"quantity": "0.50000000",
"price": "40000.0000",
"amount": "20000.0000",
"fee": "100.0000",
"tx_hash": null,
"network": null,
"idempotency_key": null,
"created_at": "2026-10-12T14:30:04Z",
"updated_at": "2026-10-12T14:30:04Z",
"completed_at": "2026-10-12T14:30:04Z",
"created_by": "43ac9e63-96c4-5f50-b788-dfbc6b265375",
"updated_by": "43ac9e63-96c4-5f50-b788-dfbc6b265375"
},
"meta": {
"timestamp": "2026-10-12T14:30:04Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}Expired quote (400 Bad Request):
{
"success": false,
"errors": [
{
"code": "QUOTE_EXPIRED",
"message": "The quote expired at 2026-10-12T14:30:15Z. Request a new quote.",
"source": { "pointer": "/quote_id" }
}
],
"meta": {
"timestamp": "2026-10-12T14:31:02Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X POST "https://api.sandbox.sovera.io/sovx/v1/users/{{walletID}}/trading/orders" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_USER_TOKEN" \
-H "x-idempotency-key: 9a7c3e1f-5b2d-4c8e-a6f0-1d3b5e7f9a2c" \
-d '{
"quote_id": "be27cd3f-3276-523e-a48c-f0661a079e72"
}'Errors: 400 QUOTE_EXPIRED (source.pointer /quote_id), or BAD_REQUEST for another rejection at execution · 401 UNAUTHORIZED · 404 WALLET_NOT_FOUND or QUOTE_NOT_FOUND · 409 IDEMPOTENCY_IN_PROGRESS · 422 INVALID_STATE_TRANSITION (quote already executed, source.pointer /quote_id), VALIDATION_ERROR, or IDEMPOTENCY_KEY_REUSED · 429 RATE_LIMIT_EXCEEDED · 502 BAD_GATEWAY, execution unavailable (the quote stays OPEN, so retry it).
Get Order
Read one order of the wallet. Returns the same order object as Place Order.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| wallet_id | string (UUID) | Yes | Wallet that placed the order. |
| order_id | string (UUID) | Yes | Order id. |
GET https://api.sandbox.sovera.io/sovx/v1/users/{{walletID}}/trading/orders/{{orderID}}Success Response (200 OK):
{
"success": true,
"data": {
"id": "168fc813-35b8-5447-89ed-07ec366f33c6",
"object": "order",
"status": "COMPLETED",
"status_reason": null,
"quote_id": "be27cd3f-3276-523e-a48c-f0661a079e72",
"transaction_id": "be2fa389-4b35-5c80-a830-2e849e9df31d",
"user_id": "43ac9e63-96c4-5f50-b788-dfbc6b265375",
"wallet_id": "90f904da-dc14-535e-b2db-56be50db02c8",
"customer_id": "corp-redundant-firewall",
"pair": "BTC/USD",
"base_currency": "BTC",
"quote_currency": "USD",
"side": "BUY",
"quantity": "0.50000000",
"price": "40000.0000",
"amount": "20000.0000",
"fee": "100.0000",
"tx_hash": null,
"network": null,
"idempotency_key": "9a7c3e1f-5b2d-4c8e-a6f0-1d3b5e7f9a2c",
"created_at": "2026-10-12T14:30:04Z",
"updated_at": "2026-10-12T14:30:05Z",
"completed_at": "2026-10-12T14:30:05Z",
"created_by": "43ac9e63-96c4-5f50-b788-dfbc6b265375",
"updated_by": "43ac9e63-96c4-5f50-b788-dfbc6b265375"
},
"meta": {
"timestamp": "2026-10-12T14:32:10Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X GET "https://api.sandbox.sovera.io/sovx/v1/users/{{walletID}}/trading/orders/{{orderID}}" \
-H "Authorization: Bearer YOUR_USER_TOKEN"Errors: 401 UNAUTHORIZED · 404 WALLET_NOT_FOUND or ORDER_NOT_FOUND · 429 RATE_LIMIT_EXCEEDED.
List Orders
List the wallet's orders, newest first. A wallet without orders returns an empty data array. The list uses cursor pagination and the filter grammar; each item is an order object.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| filter[limit] | integer | No | Page size. Default 25, maximum 100. |
| cursor | string | No | Opaque token from meta.pagination.navigation. Echo it back unchanged. |
| filter[order] | string | No | created_at or updated_at, then ASC or DESC. Default created_at DESC. |
| filter[where][status][eq] | string | No | PENDING, PROCESSING, COMPLETED, FAILED, or CANCELLED. Also [inq][]. |
| filter[where][pair][eq] | string | No | BASE/QUOTE, e.g. BTC/USD. Also [inq][]. |
| filter[where][idempotency_key][eq] | string | No | Finds the order created with that x-idempotency-key. |
| filter[where][created_at][gte] | string | No | UTC date-time ending in Z. Also [lte] and [between][]. |
Up to six filter conditions per request. Any other field, operator, or filter[skip] returns 422 with source.parameter naming the parameter.
GET https://api.sandbox.sovera.io/sovx/v1/users/{{walletID}}/trading/orders?filter[where][pair][eq]=BTC/USD&filter[where][status][inq][]=PENDING&filter[where][status][inq][]=PROCESSING&filter[limit]=25Success Response (200 OK):
{
"success": true,
"data": [
{
"id": "168fc813-35b8-5447-89ed-07ec366f33c6",
"object": "order",
"status": "PENDING",
"status_reason": null,
"quote_id": "be27cd3f-3276-523e-a48c-f0661a079e72",
"transaction_id": "be2fa389-4b35-5c80-a830-2e849e9df31d",
"user_id": "43ac9e63-96c4-5f50-b788-dfbc6b265375",
"wallet_id": "90f904da-dc14-535e-b2db-56be50db02c8",
"customer_id": "corp-redundant-firewall",
"pair": "BTC/USD",
"base_currency": "BTC",
"quote_currency": "USD",
"side": "BUY",
"quantity": "0.50000000",
"price": "40000.0000",
"amount": "20000.0000",
"fee": "100.0000",
"tx_hash": null,
"network": null,
"idempotency_key": "9a7c3e1f-5b2d-4c8e-a6f0-1d3b5e7f9a2c",
"created_at": "2026-10-12T14:30:04Z",
"updated_at": "2026-10-12T14:30:04Z",
"completed_at": null,
"created_by": "43ac9e63-96c4-5f50-b788-dfbc6b265375",
"updated_by": "43ac9e63-96c4-5f50-b788-dfbc6b265375"
}
],
"meta": {
"timestamp": "2026-10-12T14:35:00Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"pagination": {
"records": { "limit": 25, "has_next": false, "has_previous": false },
"navigation": { "next": null, "previous": null }
}
}
}When more orders exist, has_next is true and navigation.next holds the path for the next page, with your filters and a cursor.
curl -G "https://api.sandbox.sovera.io/sovx/v1/users/{{walletID}}/trading/orders" \
-H "Authorization: Bearer YOUR_USER_TOKEN" \
--data-urlencode "filter[where][pair][eq]=BTC/USD" \
--data-urlencode "filter[limit]=25"Errors: 401 UNAUTHORIZED · 404 WALLET_NOT_FOUND · 422 VALIDATION_ERROR (source.parameter names the filter) · 429 RATE_LIMIT_EXCEEDED.
Withdrawals
Send crypto off a user's account: initiate a withdrawal on a network, confirm it with a verification code, resend the code, cancel it, and track it until it settles.
Customer Management
Create, read, update, and delete your customer account, and issue or revoke the API credentials it authenticates with.