Introduction
Welcome to the Cape Crypto API Documentation. You can use our full API suite to integrate with us.
Create and cancel orders, view your account balance, stream orderbook and account updates over websocket, and perform all other actions you can perform from the apps.
🚀 Looking for trading features that we haven't added yet? We'll add them for you! Reach out to us
Looking for a Hummingbot connector for Cape Crypto? We have one! Reach out to us
Getting started
The API suite consists of public and private endpoints. Private endpoints require your requests to be signed for authentication. In order to sign your requests, you will need to create and API key and secret, and have 2FA enabled:
- Sign into your account Cape Crypto
- Head to the Profile page by clicking the profile icon at the top right
- Enable 2FA
- Create a new API key
- Save your new API key and Secret and do not share it with anyone
Use your API key and secret to sign your requests for all private endpoints. (Public endpoints do not require authentication)
All requests unless otherwise noted (EG for sub-account / customer management) use the following base URLs:
- Base url REST:
https://trade.capecrypto.com/api/v2/atlas - Base url Websocket:
wss://trade.capecrypto.com/api/v2/websocket
For Sub-account and customer management (noted in the routes below):
- Base url:
https://trade.capecrypto.com/api/v2/persona - Base url:
https://trade.capecrypto.com/api/v2/verification
Authentication
Authentication:
// generateHeaders.js
const crypto = require("crypto");
const apiKey = "123456...";
const secret = "ABCDEF...";
module.exports = async (subAccountUid = {}) => {
const timeStamp = new Date().getTime();
const queryString = `${timeStamp}${apikey}`;
const signature = crypto
.createHmac("sha256", secret)
.update(queryString)
.digest("hex");
return {
"Content-Type": "application/json;charset=utf-8",
"X-Auth-Apikey": apikey,
"X-Auth-Nonce": timeStamp,
"X-Auth-Signature": signature,
"X-Auth-Signature": signature,
// Only for sub-account / customer requests:
"X-Auth-Subaccount-UID": subAccountUid,
};
};
To securely access the private endpoints, each request requires authentication headers.
Generate a new signature for each new request, and add these headers to the request.
- X-Auth-Nonce: Current timestamp in milliseconds UTC time.
- X-Auth-Apikey: Your API key
- X-Auth-Signature: HMAC-SHA256 encrypted string of the combined timestamp, apikey and secret (see JS example)
Acting on Behalf of Sub-Accounts or Customers
When making requests on behalf of a sub-account or customer, include an additional optional header:
- X-Auth-Subaccount-Uid: The UID of the sub-account or customer you want to act as
This allows the parent account (or merchant/aggregator) to make authenticated requests as if they were the sub-account or customer. The regular authentication headers (API key, nonce, signature) must still be included.
Example using Authentication
POST Create order
// generateHeaders.js
const crypto = require("crypto");
const apiKey = "123456...";
const secret = "ABCDEF...";
module.exports = async () => {
const timeStamp = new Date().getTime();
const queryString = `${timeStamp}${apikey}`;
const signature = crypto
.createHmac("sha256", secret)
.update(queryString)
.digest("hex");
return {
"Content-Type": "application/json;charset=utf-8",
"X-Auth-Apikey": apikey,
"X-Auth-Nonce": timeStamp,
"X-Auth-Signature": signature,
};
};
Send the authenticated request:
// createOrder.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
createOrder = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "POST";
const path = `/api/v2/atlas/market/orders`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const orderData = {
market: "btczar",
side: "sell",
volume: "0.00001",
ord_type: "limit",
price: "800000",
post_only: true,
custom_order_id: "302b0494-1a06-4089-846c-0c6b13f1b661",
};
const axiosConfig = {
method: method,
url: url,
headers: headers,
data: orderData,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
Create order response:
{
"id": 173757,
"uuid": "7e86a4fa-fcae-4953-b9bc-d6f328beea48",
"side": "sell",
"ord_type": "limit",
"price": "800000.0",
"avg_price": "0.0",
"state": "pending",
"market": "btczar",
"created_at": "2023-12-08T08:46:59+01:00",
"updated_at": "2023-12-08T08:46:59+01:00",
"origin_volume": "0.00001",
"remaining_volume": "0.00001",
"executed_volume": "0.0",
"trades_count": 0,
"custom_order_id": "302b0494-1a06-4089-846c-0c6b13f1b661"
}
https://trade.capecrypto.com/api/v2/atlas/market/orders
Create a new order on your account by signing your request and submitting the request
POST Create order as sub-account
// generateHeadersWithSubAccount.js
const crypto = require("crypto");
const apiKey = "123456...";
const secret = "ABCDEF...";
const subAccountUid = "CCT1234567"; // Sub-account or customer UID
module.exports = async () => {
const timeStamp = new Date().getTime();
const queryString = `${timeStamp}${apiKey}`;
const signature = crypto
.createHmac("sha256", secret)
.update(queryString)
.digest("hex");
return {
"Content-Type": "application/json;charset=utf-8",
"X-Auth-Apikey": apiKey,
"X-Auth-Nonce": timeStamp,
"X-Auth-Signature": signature,
"X-Auth-Subaccount-Uid": subAccountUid, // Act on behalf of sub-account/customer
};
};
Send the authenticated request as sub-account:
// createOrderAsSubAccount.js
const axios = require("axios");
const generateHeaders = require("./generateHeadersWithSubAccount");
createOrder = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "POST";
const path = `/api/v2/atlas/market/orders`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const orderData = {
market: "btczar",
side: "buy",
volume: "0.00001",
ord_type: "limit",
price: "750000",
};
const axiosConfig = {
method: method,
url: url,
headers: headers,
data: orderData,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
https://trade.capecrypto.com/api/v2/atlas/market/orders
When the X-Auth-Subaccount-Uid header is included, the order is created on behalf of the specified sub-account or customer. The parent account's API credentials are used for authentication, but the operation is performed in the context of the sub-account.
Public request example
GET Get available markets
const axios = require("axios");
getMarkets = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "GET";
const path = `/api/v2/atlas/public/markets`;
const url = `${baseUrl}${path}`;
const axiosConfig = {
method: method,
url: url,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
getMarkets();
Markets response:
[
{
"id": "btczar",
"name": "BTC/ZAR",
"base_unit": "btc",
"quote_unit": "zar",
"min_price": "200000.0",
"max_price": "1200000.0",
"min_amount": "0.00001",
"amount_precision": 8,
"price_precision": 2,
"state": "enabled"
},
{
"id": "ethzar",
"name": "ETH/ZAR",
"base_unit": "eth",
"quote_unit": "zar",
"min_price": "10000.0",
"max_price": "100000.0",
"min_amount": "0.0001",
"amount_precision": 8,
"price_precision": 2,
"state": "enabled"
},
{
"id": "xrpzar",
"name": "XRP/ZAR",
"base_unit": "xrp",
"quote_unit": "zar",
"min_price": "1.0",
"max_price": "100.0",
"min_amount": "1.0",
"amount_precision": 6,
"price_precision": 2,
"state": "enabled"
},
{
"id": "usdczar",
"name": "USDC/ZAR",
"base_unit": "usdc",
"quote_unit": "zar",
"min_price": "14.0",
"max_price": "29.0",
"min_amount": "0.5",
"amount_precision": 2,
"price_precision": 2,
"state": "enabled"
}
]
GET https://trade.capecrypto.com/api/v2/atlas/public/markets
Returns a list of all available markets
Public
GET Alive
/public/health/alive
Description
Check that the trade engine is running. Occasionally the trade engine will be temporarily taken offline for udpates and general maintenance. Maintenence will usually not take longer than a few minutes.
Responses
| Code | Description |
|---|---|
| 200 | Trade engine is running, trading is available |
GET Get VASPs
/public/vasps
Description
Retrieve a paginated list of active Virtual Asset Service Providers (VASPs) registered in the system. VASPs are used for travel rule compliance when creating beneficiaries with cryptocurrency addresses.
When creating a beneficiary with travel rule information, use the id from this endpoint as the vasp_id in the beneficiary data.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| page | query | Page number (default: 1) | No | integer |
| limit | query | Results per page (default: 25, max: 100) | No | integer |
Response Headers
| Header | Description |
|---|---|
| Page | Current page number |
| Per-Page | Number of results per page |
| Total | Total number of VASPs available |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | List of active VASPs | [ VASP ] |
GET Ticker
/public/markets/{market}/tickers
Description
Get ticker of specific market.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| market | path | Yes | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 Returns a ticker. | Ticker |
GET All tickers
/public/markets/tickers
Description
Get ticker of all markets
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Returns an array of tickers. | Ticker |
GET K Line
/public/markets/{market}/k-line
Description
Get K-Line (OHLC) of specific market.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| market | path | Yes | string | |
| period | query | Time period of K line, default to 1. You can choose between 1, 5, 15, 30, 60, 120, 240, 360, 720, 1440, 4320, 10080 | No | integer |
| time_from | query | Timestamp in ms. If set, only k-line data after that time will be returned. | No | integer |
| time_to | query | Timestamp in ms. If set, only k-line data till that time will be returned. | No | integer |
| limit | query | Limit the number of returned data points default to 30. Ignored if time_from and time_to are given. | No | integer |
Responses
| Code | Description |
|---|---|
| 200 | Returns K-Line (OHLC) of market. |
GET Orderbook
/public/markets/{market}/depth
Description
Get the orderbook for the specified market eg. 'btczar'. Asks and bids are sorted away from the spread, ie asks are sorted low to high, and bids are sorted high to low. Results are returned in tupples of [price, amount] in the base currency eg. 'btc'.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| market | path | Yes | string | |
| limit | query | Limit the number of returned price levels. Default 100. | No | integer |
Responses
| Code | Description |
|---|---|
| 200 | Get orderbook of specified market |
GET Market trades
/public/markets/{market}/trades
Description
Get recent trades for the specified market, results are paginated
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| market | path | Yes | string | |
| limit | query | Limit the number of returned trades. Default to 100. | No | integer |
| time_from | query | Timestamp in ms. Return trades executed after the timestamp. | No | integer |
| time_to | query | Timestamp in ms. Return trades executed before the timestamp. | No | integer |
| page | query | Paginated page number. | No | integer |
| order_by | query | Default is desc (newest to oldest). Options are [asc desc] | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get recent trades on market. | [ Trade ] |
GET Markets
/public/markets
Description
Get all available markets.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| limit | query | Limit the number of returned paginations. Defaults to 100. | No | integer |
| page | query | Paginated page number. | No | integer |
| ordering | query | Order of results by field name (see order_by below), defaults to 'asc'. | No | string |
| order_by | query | Name of the field to order by. [id position] | No | string |
| base_unit | query | Return only markets that have this currency as the base unit eg. 'btc' | No | string |
| quote_unit | query | Return only markets that have this currency as the quote unit eg. 'zar' | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get all available markets. | [ Market ] |
GET All Currencies
/public/currencies
Description
Get list of currencies
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| limit | query | Limit the number of returned paginations. Defaults to 100. | No | integer |
| page | query | Paginated page number. | No | integer |
| type | query | Currency type | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get list of currencies | [ Currency ] |
GET Single Currency
/public/currencies/{id}
Description
Get a single currency
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| id | path | Currency code. | Yes | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get a currency | Currency |
Account
GET Get transaction history
/account/transactions
Description
Retrieve your complete transaction history with flexible filtering options. This endpoint returns all types of transactions including deposits, withdrawals, trades, internal transfers, referrals, and maker rewards.
Transactions can be filtered by currency (matches both credit and debit currencies), transaction type, time range, and specific transaction ID. Results are paginated and can be ordered by creation time.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| currency | query | Filter by currency code (matches credit or debit currency, e.g., "btc", "zar") | No | string |
| type | query | Filter by transaction type: 'deposit', 'withdraw', 'internal_transfer', 'trade', 'referral', 'maker_reward' | No | string |
| txid | query | Filter by specific transaction ID | No | string |
| time_from | query | Start timestamp (Unix timestamp in seconds) | No | integer |
| time_to | query | End timestamp (Unix timestamp in seconds) | No | integer |
| order_by | query | Sort order: 'asc' or 'desc' (default: 'desc') | No | string |
| page | query | Page number (default: 1, min: 1) | No | integer |
| limit | query | Results per page (default: 50, min: 1, max: 1000) | No | integer |
Response Headers
| Header | Description |
|---|---|
| Total | Total number of transactions |
| Page | Current page number |
| Per-Page | Number of results per page |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Transaction history retrieved | [ Transaction ] |
| 401 | Authentication required | Error array |
| 422 | Validation error | Error array |
Example Request
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
getTransactions = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "GET";
const path = `/api/v2/account/transactions?currency=btc&type=deposit&limit=25&page=1`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const axiosConfig = {
method: method,
url: url,
headers: headers,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
getTransactions();
POST Create new beneficiary
/account/beneficiaries
Description
Create a new beneficiary for cryptocurrency or fiat withdrawals. Beneficiaries can be cryptocurrency wallet addresses (with required travel rule information) or bank accounts for fiat transfers. All beneficiaries created via the API are automatically activated and ready for use.
For cryptocurrency beneficiaries with travel rule requirements, you can specify whether the wallet is your own (is_own), unhosted (is_unhosted), or associated with a Virtual Asset Service Provider (VASP). Use the Get VASPs endpoint to retrieve available VASP IDs.
Parameters
| Name | Located in | Description | Required | Schema | Max Length |
|---|---|---|---|---|---|
| currency | formData | Currency code (e.g., "btc", "eth", "zar", "gbp") | Yes | string | 10 chars |
| name | formData | Display name for the beneficiary | Yes | string | 64 chars |
| description | formData | Optional notes about the beneficiary | No | string | 255 chars |
| blockchain_key | formData | Blockchain identifier (defaults to currency's blockchain if omitted) | No | string | 255 chars |
| withdraw_method_id | formData | Specific withdrawal method ID (auto-selected if omitted) | No | integer | - |
| data | formData | Address/account details in JSON format (structure varies by currency type - see tables below) | Yes | json | - |
Cryptocurrency data Object Fields
| Field | Type | Required | Description |
|---|---|---|---|
| address | string | Yes | Cryptocurrency wallet address |
| tag | string | No | Destination tag (for XRP, Stellar, etc.) - max: 4294967295 |
| is_own | boolean | No | Travel rule: Is this your own wallet? |
| is_unhosted | boolean | No | Travel rule: Is this an unhosted wallet? |
| vasp_id | number | No | Travel rule: VASP ID (obtain from Get VASPs endpoint) |
| recipient_data | object | No | Travel rule: Recipient information (see travel rule fields) |
Fiat data Object Fields
| Field | Type | Required | Description |
|---|---|---|---|
| full_name | string | Yes | Account holder's full legal name (mandatory for fiat) |
| account_number | string | No | Bank account number |
| bank_name | string | No | Name of the bank |
| bank_swift_code | string | No | SWIFT/BIC code |
| bank_routing_number | string | No | Routing number (US banks) |
| iban | string | No | IBAN (International Bank Account Number) |
| bank_address | string | No | Bank's physical address |
| bank_city | string | No | Bank's city |
| intermediary_bank_swift | string | No | Intermediary bank SWIFT code (for international transfers) |
| account_holder_name | string | No | Account holder name (may differ from full_name) |
| account_type | string | No | Type of account (checking, savings, etc.) |
Travel Rule recipient_data Object (Nested within data) (required for Cryptocurrency Beneficiaries)
| Field | Type | Required | Description |
|---|---|---|---|
| first_name | string | Yes (for Individuals) | Recipient's first name |
| last_name | string | Yes (for Individuals) | Recipient's last name |
| country | string | Yes | Recipient's country code |
| identity_type | string | Yes | Individual or Business |
| company_name | string | Yes (for Businesses) | Company name (for business recipients) |
Request Examples
Example 1: Crypto - Bitcoin
{
"currency": "btc",
"name": "My Bitcoin Wallet",
"description": "Personal cold storage wallet",
"data": {
"address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
}
}
Example 2: Crypto - XRP with Destination Tag
{
"currency": "xrp",
"name": "Exchange XRP Account",
"description": "My Binance XRP deposit",
"data": {
"address": "rN7n7otQDd6FczFgLdlqtyMVrn3qHwuSaDcqC",
"tag": "123456789"
}
}
Example 3: Crypto - Ethereum with Travel Rule Info
{
"currency": "eth",
"name": "Friend's Wallet",
"description": "John's Ethereum wallet",
"data": {
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"is_own": false,
"is_unhosted": true,
"recipient_data": {
"first_name": "John",
"last_name": "Doe",
"country": "US"
}
}
}
Example 4: Fiat - ZAR (South African Rand)
{
"currency": "zar",
"name": "South African Bank Account",
"description": "Personal savings account",
"data": {
"full_name": "Thabo Mbeki",
"account_number": "1234567890",
"bank_name": "Standard Bank",
"bank_swift_code": "SBZAZAJJ",
"bank_address": "30 Baker Street",
"bank_city": "Johannesburg",
"account_type": "savings"
}
}
Example 5: Fiat - GBP SWIFT Transfer
{
"currency": "gbp",
"name": "UK Business Account",
"description": "Company bank account",
"data": {
"full_name": "John Smith",
"account_number": "12345678",
"bank_name": "Barclays Bank",
"bank_swift_code": "BARCGB22",
"bank_address": "1 Churchill Place",
"bank_city": "London",
"account_type": "checking"
}
}
Example 6: Fiat - EUR IBAN Transfer
{
"currency": "eur",
"name": "EU Partner Account",
"description": "European supplier payment",
"data": {
"full_name": "Jean Dupont",
"iban": "FR1420041010050500013M02606",
"bank_name": "BNP Paribas",
"bank_swift_code": "BNPAFRPP",
"bank_city": "Paris"
}
}
Example 7: Fiat - International with Intermediary Bank
{
"currency": "usd",
"name": "International Vendor",
"description": "Asian supplier USD payment",
"data": {
"full_name": "ABC Trading Company Ltd",
"account_number": "9876543210",
"bank_name": "Asia Commercial Bank",
"bank_swift_code": "ACBVSGSG",
"intermediary_bank_swift": "CHASUS33",
"bank_address": "123 Business Street",
"bank_city": "Singapore"
}
}
Important Notes
- API-created beneficiaries are automatically activated (state =
active) and do not require PIN activation - Fiat beneficiaries must include
full_namein thedataobject (mandatory) - Cryptocurrency addresses are validated against the blockchain service before creation
- XRP destination tags must be unsigned int32 (maximum: 4294967295)
- Duplicate addresses are not allowed for the same currency/blockchain combination
Responses
| Code | Description | Schema |
|---|---|---|
| 201 | Create new beneficiary | Beneficiary |
PATCH Activate beneficiary
/account/beneficiaries/{id}/activate
Description
Activates a new beneficiary with pin emailed during beneficiary creation.
Note: This activation step is only required for beneficiaries created through the web interface. Beneficiaries created via the API are automatically activated and do not require this step.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| id | path | Beneficiary Identifier | Yes | integer |
| pin | formData | Pin code for beneficiary activation | Yes | integer |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Activates beneficiary with pin | Beneficiary |
DELETE Delete beneficiary
/account/beneficiaries/{id}
Description
Delete beneficiary
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| id | path | Beneficiary Identifier | Yes | integer |
Responses
| Code | Description |
|---|---|
| 204 | Delete beneficiary |
GET Beneficiary by id
/account/beneficiaries/{id}
Description
Get beneficiary by id
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| id | path | Beneficiary Identifier | Yes | integer |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get beneficiary by id | Beneficiary |
GET List Beneficiaries
/account/beneficiaries
Description
Get list of user beneficiaries
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| currency | query | Beneficiary currency code. | No | string |
| state | query | Defines either beneficiary active - user can use it to withdraw moneyor pending - requires beneficiary activation with pin. | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get list of user beneficiaries | [ Beneficiary ] |
GET Withdraw quote
/account/withdraws/quote/{currency}
/account/withdraws/quote/{currency}?bolt=ln...
Description
Get the expected fee to withdraw the specified currency. Submit the quote_id with your withdraw request payload to honor that fee during the withdrawal. Quotes are currently valid for 90 seconds, ensure you submit the withdrawal request before the expires_at parameter.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| currency | path | Currency code. | Yes | string |
| bolt | query | For BTC Lightning payments, supply the Bolt11 invoice. Fees are returned in BTC. | No | integer |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Returns a withdraw quote | [ Withdraw quote ] |
| 422 | Unproccesable entity | Error array containing the reason why the quote could not be created |
POST New withdrawal
/account/withdraws
Description
Creates new withdrawal to active beneficiary.
Note: OTP is not required when creating withdrawals via the API. The OTP requirement only applies to withdrawals created through the web app, however 2FA must still be enabled on the account to enable withdraws.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| beneficiary_id | formData | ID of active Beneficiary. | Yes | integer |
| currency | formData | The currency code matching the beneficiary. | Yes | string |
| amount | formData | The amount to withdraw. | Yes | double |
| note | formData | ZAR withdrawals only. Use 'same_day_rtc' for Faster withdraws | No | string |
Responses
| Code | Description |
|---|---|
| 201 | Creates new withdrawal. |
GET Withdrawal history
/account/withdraws
Description
Get your withdraw history, paginated.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| currency | query | Currency code. | No | string |
| limit | query | Number of withdraws per page (defaults to 100, maximum is 100). | No | integer |
| state | query | Filter by withdraw state. [ Withdraw states ] | No | string |
| rid | query | Destination address (blockchain withdraws only). | No | string |
| page | query | Page number (defaults to 1). | No | integer |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | List your withdraws. | [ Withdraw ] |
GET Get deposit methods
/api/v2/payments/deposits/methods
Description
Get all available deposit methods for the authenticated user. Returns deposit methods for both cryptocurrencies and fiat currencies, filtered based on the user's KYC level and remaining transaction limits.
Each method includes minimum and maximum amounts, fees, provider information, and remaining limits for different time periods (daily, weekly, monthly, yearly).
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Array of available deposit methods. | [ Deposit Method ] |
GET Get withdraw methods
/api/v2/payments/withdraws/methods
Description
Get all available withdrawal methods for the authenticated user. Returns withdrawal methods for both cryptocurrencies and fiat currencies, filtered based on the user's KYC level and remaining transaction limits.
Each method includes minimum and maximum amounts, fees, provider information, processor details, and remaining limits for different time periods (daily, weekly, monthly, yearly).
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Array of available withdrawal methods. | [ Withdraw Method ] |
POST Redeem voucher
/payments/vouchers/new
Description
Redeem a voucher code.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| payment_method_id | query | The id of this deposit method (see Instant FIAT deposit methods) | Yes | integer |
| amount | query | The amount to redeem, eg 10 redeems R10 | Yes | double |
| voucher_pin | query | The voucher redemption code. | Yes | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 201 | Voucher redemption. | Voucher redemption |
GET Deposit address
/account/deposit_address/{currency}
Description
Fetch the deposit address for the specified currency.
Note: If your currency deposit address has not been created yet, the result may return empty while the address is being created. If it is empty, please try again in a few seconds.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| currency | path | The currency to receive. | Yes | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Deposit address. (May be empty on first request for new accounts, see note above). | Deposit |
GET Deposit by ID
/account/deposits/{id}
Description
Get details of a specific deposit by its internal deposit ID.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| id | path | Deposit ID | Yes | integer |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get details of specific deposit. | Deposit |
POST Submit deposit travel rule
/account/deposits/{id}/travel_rule
Description
Submit travel rule compliance information for a cryptocurrency deposit that is on hold pending travel rule verification.
Note: This endpoint is only applicable for cryptocurrency deposits in the on_hold_travel_rule_customer state. The deposit must be a coin deposit (not fiat).
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| id | path | Deposit ID | Yes | string |
| is_own | formData | Is this your own wallet? | Yes | boolean |
| is_unhosted | formData | Is this an unhosted wallet? (Cannot be true if vasp_id is provided) | Yes | boolean |
| vasp_id | formData | VASP ID from the system (mutually exclusive with custom_vasp fields) | No | integer |
| custom_vasp_name | formData | Custom VASP name (required if not using vasp_id and not unhosted) | No | string |
| custom_vasp_url | formData | Custom VASP URL (required if not using vasp_id and not unhosted) | No | string |
| originator_data | formData | Originator information in JSON format (required if is_own is false, see fields below) | No | json |
Originator Data Fields:
When is_own is false, you must provide originator information:
| Field | Type | Required | Description |
|---|---|---|---|
| identity_type | string | Yes | Type of identity (e.g., "passport", "id") |
| first_name | string | Yes (for Individuals) | Originator's first name |
| last_name | string | Yes (for Individuals) | Originator's last name |
| company_name | string | Yes (for Businesses) | Company name (for business originators) |
| country | string | Yes | ISO country code (e.g., "US", "ZA", "GB") |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Travel rule submitted successfully. Deposit transitions to hold state. | Deposit |
| 422 | Validation error or deposit not eligible (wrong state, already submitted). | Error array |
GET Deposit history
/account/deposits
Description
Get your deposit history. You can filter by specific deposit transaction using the txid query parameter, or fetch all deposits with optional filters for currency and state
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| currency | query | Currency code | No | string |
| state | query | Filter deposits by deposit state States | No | string |
| txid | query | Deposit transaction id. | No | string |
| limit | query | Number of deposits per page (defaults to 100, maximum is 100). | No | integer |
| page | query | Page number (defaults to 1). | No | integer |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get your deposit history. | [ Deposit ] |
GET Balance
/account/balances/{currency}
Description
Get single account balance by currency code eg. 'btc'
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| currency | path | The currency code. | Yes | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get account balance by currency | Account |
GET All balances
/account/balances
Description
Get account balances for all currencies
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| limit | query | Limit the number of returned paginations. Defaults to 100. | No | integer |
| page | query | Paginated page number. | No | integer |
| nonzero | query | Filter non zero balances. | No | Boolean |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Array of balances | [ Account ] |
POST New lightning payment
/account/lightning/pays
Description
Submits a payment request to pay a BTC Lightning BOLT11 invoice (withdraws this balance from your BTC account).
As the payment is requested and validated on the lightning network, please query your withdrawal history to confirm that the payment succeeded.
Note: OTP is not required when creating withdrawals via the API. The OTP requirement only applies to withdrawals created through the web app, however 2FA must still be enabled on the account to enable withdraws.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| bolt | query | The bolt11 invoice to pay (either bolt or address required) | No | string |
| amount | query | The amount to of the invoice in SATs. Sanity check is performed against the supplied bolt11 | Yes | double |
| quote_id | query | The quote_id of the withdrawal quote, see Withdraw quote | No | string |
| member_description | query | Personal description of this payment for your records | No | string |
| description | query | Payment description (visible to recipient if applicable) | No | string |
| data | formData | Travel rule information in JSON format (see travel rule fields below) | No | json |
Travel Rule Data (Optional):
For compliance with travel rule requirements, you can include recipient information in the data parameter as a JSON object:
{
"is_own": false,
"is_unhosted": true,
"vasp_id": 123,
"address": "lnbc...", // Lightning address or BOLT11
"recipient_data": {
"first_name": "John",
"last_name": "Doe",
"company_name": "Acme Corp",
"address": "123 Main St",
"country": "US",
"identity_type": "Passport"
}
}
Travel Rule Fields:
| Field | Type | Description |
|---|---|---|
| is_own | boolean | Is this your own wallet? |
| is_unhosted | boolean | Is this an unhosted wallet? |
| vasp_id | number | VASP ID (obtain from Get VASPs endpoint) |
| address | string | Lightning address or BOLT11 invoice |
| recipient_data | object | Recipient information (see fields below) |
Recipient Data Fields:
| Field | Type | Description |
|---|---|---|
| first_name | string | Recipient's first name |
| last_name | string | Recipient's last name |
| company_name | string | Company name (for business recipients) |
| address | string | Physical address |
| country | string | Country code (e.g., "US", "ZA") |
| identity_type | string | Type of identification document |
Responses
| Code | Description | Schema |
|---|---|---|
| 201 | Lightning payment. | [ Lightning payment ] |
POST Create a Lightning invoice
/account/lightning/invoices
Description
Create a new lightning invoice to be paid (receive) over the BTC Lightning network.
You can optionally specify a conversion_percent (1 = 100%, 0.1 = 10%) to automatically convert once the invoice is paid. The conversion percent should be such that the conversion amount is greater than or equal to the minimum order amount for the market eg. 'btczar'. For example if receiving BTC 0.00001, and the minimum order amount on 'btczar' is 0.00001, your conversion percent would have to be 1 (100%). Anything less would cause the invoice creation to fail, as the conversion amount would be below the minimum market order amount.
The invoice expires in 1 hour.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| amount | query | The amount to of the invoice in SATs. Sanity check is performed against the supplied bolt11 | Yes | double |
| conversion_percent | query | Percent of the received amount to be automatically converted to FIAT | No | double |
| description | query | The description for the invoice (visible on the Bolt11 invoice to the payer). If left blank the default Cape Crypto Invoice is used | No | string |
| member_description | query | Personal description of this invoice for your records | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 201 | Lightning invoice. | [ Lightning invoice ] |
Sub-Accounts
Note: Sub-account endpoints use the /api/v2/persona base path instead of /api/v2/atlas.
Sub-Account actions: To make API requests for a sub-account (e.g., placing trades, checking balances), include the X-Auth-Subaccount-Uid header with the sub-account's UID. Your parent account's API credentials are used for authentication, but operations are performed in the sub-account's context.
POST Create Sub-Account
// createSubAccount.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
createSubAccount = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "POST";
const path = `/api/v2/persona/resource/sub_accounts`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const subAccountData = {
account_name: "Trading Account 1",
deposit_action: "transfer_to_parent",
};
const axiosConfig = {
method: method,
url: url,
headers: headers,
data: subAccountData,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
createSubAccount();
/persona/resource/sub_accounts
Description
Create a new sub-account that inherits the parent's KYC verification. Sub-accounts cannot login independently and are managed by the parent account.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| account_name | formData | Display name for the sub-account (1-100 characters) | Yes | string |
| deposit_action | formData | JSON object defining automated deposit handling (see deposit action options below) | No | json |
| data | formData | Optional additional data in JSON format | No | json |
Deposit Action Options:
Configure automatic actions for deposits received by this sub-account.
The deposit_action parameter accepts a JSON object with the following structure:
{
"action": "none|aggregate|convert_and_withdraw|aggregate_convert_and_withdraw",
"target_currency": "currency_code", // Required for convert actions (e.g., "zar", "usd")
"beneficiary_id": 123, // Required for withdraw actions
"lightning_invoice": "lnbc...", // Alternative to beneficiary_id for Lightning withdrawals
"max_slippage": 2.0 // Optional: Max slippage percentage (0.1-10, default: 2.0)
}
Available Actions:
| Action | Description | Required Fields |
|---|---|---|
none |
No automatic action (default) | None |
aggregate |
Automatically transfer deposits to parent account | None |
convert_and_withdraw |
Convert deposit to target currency and withdraw to beneficiary | target_currency, beneficiary_id or lightning_invoice |
aggregate_convert_and_withdraw |
Transfer to parent, then convert and withdraw | target_currency, beneficiary_id or lightning_invoice |
Example with Simple Aggregate:
{
"account_name": "Trading Account 1",
"deposit_action": {
"action": "aggregate"
}
}
Example with Convert and Withdraw:
{
"account_name": "USD Conversion Account",
"deposit_action": {
"action": "convert_and_withdraw",
"target_currency": "usd",
"beneficiary_id": 789,
"max_slippage": 1.5
}
}
Responses
| Code | Description | Schema |
|---|---|---|
| 201 | Sub-account created successfully | SubAccount |
| 422 | Validation error | Error array |
GET List Sub-Accounts
// listSubAccounts.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
listSubAccounts = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "GET";
const path = `/api/v2/persona/resource/sub_accounts?page=1&limit=100`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const axiosConfig = {
method: method,
url: url,
headers: headers,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
listSubAccounts();
/persona/resource/sub_accounts
Description
Get a paginated list of the user's sub-accounts.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| page | query | Page number (defaults to 1) | No | integer |
| limit | query | Number of results per page (default: 100, max: 1000) | No | integer |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | List of sub-accounts | [ SubAccount ] |
GET Get Sub-Account by UID
// getSubAccount.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
getSubAccount = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "GET";
const subAccountUid = "CCT1234567";
const path = `/api/v2/persona/resource/sub_accounts/${subAccountUid}`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const axiosConfig = {
method: method,
url: url,
headers: headers,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
getSubAccount();
/persona/resource/sub_accounts/{uid}
Description
Retrieve a specific sub-account by its unique identifier.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| uid | path | Sub-account identifier | Yes | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Sub-account found | SubAccount |
| 404 | Not found | Error array |
PUT Update Sub-Account
// updateSubAccount.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
updateSubAccount = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "PUT";
const subAccountUid = "CCT1234567";
const path = `/api/v2/persona/resource/sub_accounts/${subAccountUid}`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const updateData = {
deposit_action: "none",
};
const axiosConfig = {
method: method,
url: url,
headers: headers,
data: updateData,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
updateSubAccount();
/persona/resource/sub_accounts/{uid}
Description
Update sub-account settings.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| uid | path | Sub-account identifier | Yes | string |
| deposit_action | formData | Action for deposits: 'none' or 'transfer_to_parent' | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Sub-account updated successfully | SubAccount |
| 404 | Not found | Error array |
| 422 | Validation error | Error array |
DELETE Delete Sub-Account
// deleteSubAccount.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
deleteSubAccount = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "DELETE";
const subAccountUid = "CCT1234567";
const path = `/api/v2/persona/resource/sub_accounts/${subAccountUid}`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const axiosConfig = {
method: method,
url: url,
headers: headers,
};
try {
const results = await axios(axiosConfig);
console.log("Sub-account deleted successfully");
} catch (err) {
console.log(err);
}
};
deleteSubAccount();
/persona/resource/sub_accounts/{uid}
Description
Soft delete a sub-account (sets state to 'deleted'). The sub-account will no longer be accessible.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| uid | path | Sub-account identifier | Yes | string |
Responses
| Code | Description |
|---|---|
| 204 | Sub-account deleted successfully |
| 404 | Not found |
Customers Management
Note: Customer management endpoints use the /api/v2/persona base path and are available for Merchants and Aggregators only. Not all accounts are enabled for customer management, please contact us at support.capecrypto.com should you with to apply to upgrade your account to a merchant or aggregator account.
Hierarchical Structure:
- Aggregators can create and manage both merchants (associated to them) and customers
- Merchants can create and manage customers under their own account
Customer Types:
- Individual customers - Personal accounts with profile information
- Business customers - Business accounts with company details and UBO information
Acting as a Customer: To make API requests for a customer (e.g., placing trades, checking balances, making withdrawals), include the X-Auth-Subaccount-Uid header with the customer's UID. Your merchant/aggregator API credentials are used for authentication, but operations are performed in the customer's context.
POST Create Individual Customer
// createIndividualCustomer.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
createIndividualCustomer = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "POST";
const path = `/api/v2/persona/resource/customers`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const customerData = {
entity_type: "individual",
email: "[email protected]",
phone_number: "+27821234567",
account_name: "John Doe Account",
// Personal Information
first_name: "John",
last_name: "Doe",
dob: "1990-01-15",
// Location Information
address: "123 Main Street, Apt 4B",
postcode: "7700",
city: "Cape Town",
country: "ZA",
nationality: "ZA",
country_of_residence: "ZA",
sa_id_number: "9001155800087",
// Professional Information (See not above for approved list)
occupation: "Software Engineer",
source_of_funds: "Salary or Wages",
// Document Information
doc_number: "M12345678",
doc_type: "Passport",
doc_expire: "2028-12-31",
doc_country: "ZA",
// PEP Declaration
is_pep_occupation: false,
is_pep_related: false,
is_pip: false,
};
const axiosConfig = {
method: method,
url: url,
headers: headers,
data: customerData,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
createIndividualCustomer();
/persona/resource/customers
Description
Create a new individual customer account. Merchants create customers under their own account. Aggregators can create customers under their own account or under a specific merchant by providing merchant_uid.
Individual customers are created in two steps:
- 1. Submitting the profile info will create the customer account,
- 2. All KYC Documents must be uploaded, separately, using the document upload endpoint (see Post Upload Individual Customer Documents section below).
Once all documents are received the customer account will be verified by Cape Crypto, and their parent cant then create payment requests, or have the customer deposit funds into their account.
Parameters
Account Information:
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| entity_type | formData | Must be 'individual' | Yes | string |
| formData | Customer email address (unique per merchant) | Yes | string | |
| phone_number | formData | Phone number in international format (e.g., +27821234567) | Yes | string |
| account_name | formData | Display name for the account | No | string |
| merchant_uid | formData | Merchant UID (aggregators only - create under specific merchant) | No | string |
| first_name | formData | Customer first name | Yes | string |
| last_name | formData | Customer last name | Yes | string |
| dob | formData | Date of birth in YYYY-MM-DD format (must be 18+) | Yes | string |
| address | formData | Full street address | Yes | string |
| postcode | formData | Postal/ZIP code | Yes | string |
| city | formData | City name | Yes | string |
| country | formData | ISO country code (2 chars, e.g., 'ZA', 'US') | Yes | string |
| nationality | formData | Nationality ISO country code (2 chars) | Yes | string |
| country_of_residence | formData | Country of residence ISO code (2 chars) | Yes | string |
| sa_id_number | formData | South African ID number | Yes if nationality='ZA' | string |
| occupation | formData | Customer occupation (e.g., 'Software Engineer', 'Teacher', 'Business Owner') | Yes | string |
| source_of_funds | formData | Source of funds (e.g., 'Salary or Wages', 'Business Income', 'Investment Income') | Yes | string |
| is_pep_occupation | formData | Is the customer a PEP by occupation? (default: false) | No | boolean |
| pep_occupation_data | formData | Details if is_pep_occupation is true | Yes if is_pep_occupation=true | string |
| is_pep_related | formData | Is the customer related to a PEP? (default: false) | No | boolean |
| pep_related_data | formData | Details if is_pep_related is true | Yes if is_pep_related=true | string |
| is_pip | formData | Is the customer a Prominent Influential Person? (default: false) | No | boolean |
| pip_data | formData | Details if is_pip is true | Yes if is_pip=true | string |
| deposit_action | formData | JSON object defining automated deposit handling (see deposit action options below) | No | json |
Deposit Action Options:
The optional deposit_action parameter accepts a JSON object with the following structure:
{
"action": "none|aggregate|convert_and_withdraw|aggregate_convert_and_withdraw",
"target_currency": "currency_code", // Required for convert actions (e.g., "zar", "usd")
"beneficiary_id": 123, // Required for withdraw actions
"lightning_invoice": "lnbc...", // Alternative to beneficiary_id for Lightning withdrawals
"max_slippage": 2.0 // Optional: Max slippage percentage (0.1-10, default: 2.0)
}
Available Actions:
If no deposit action is provided, the default will be "none" which will see the deposit simply credited to the customer account, like a regular deposit.
Note: Only the "none" action is currently available, the additional actions are coming soon!
| Action | Description | Required Fields |
|---|---|---|
none |
No automatic action (default) | None |
aggregate (coming soon) |
Automatically transfer deposits to parent merchant/aggregator account | None |
convert_and_withdraw (coming soon) |
Convert deposit to target currency and withdraw to beneficiary | target_currency, beneficiary_id or lightning_invoice |
aggregate_convert_and_withdraw (coming soon) |
Transfer to parent, then convert and withdraw | target_currency, beneficiary_id or lightning_invoice |
Example with Deposit Action:
{
"entity_type": "individual",
"email": "[email protected]",
"first_name": "John",
"last_name": "Doe",
// ... other required fields ...
"deposit_action": {
"action": "convert_and_withdraw",
"target_currency": "zar",
"beneficiary_id": 456,
"max_slippage": 2.5
}
}
Responses
| Code | Description | Schema |
|---|---|---|
| 201 | Customer created successfully | Customer |
| 422 | Validation error | Error array |
POST Create Business Customer
// createBusinessCustomer.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
createBusinessCustomer = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "POST";
const path = `/api/v2/persona/resource/customers`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const customerData = {
entity_type: "business",
email: "[email protected]",
phone_number: "+27821234567",
account_name: "ACME Corporation",
business_profile: {
// Core Business Information
company_name: "ACME Corp (Pty) Ltd",
registration_number: "2020/123456/07",
registration_country: "ZA",
industry: "Financial Services",
purpose_of_account: "Cryptocurrency trading and payment processing",
expected_monthly_volume_usd: 500000,
// Additional Business Information
jurisdictions_of_operation: ["ZA", "US", "GB"],
website: "https://www.acmecorp.com",
employee_count: 25,
// Business Address
address: "123 Business Street, Suite 100",
city: "Cape Town",
postcode: "8001",
country: "ZA",
// License Information
license_status: "Licensed",
license_number: "FSP12345",
license_jurisdictions: ["ZA"],
},
// Ultimate Beneficial Owners (at least 1 required)
ubos: [
{
// Personal Information
email: "[email protected]",
first_name: "Jane",
last_name: "Smith",
phone_number: "+27821111111",
dob: "1985-05-15",
// Residence Information
address: "456 Residential Ave",
postcode: "7700",
city: "Cape Town",
country: "ZA",
nationality: "ZA",
id_number: "8505155800088",
// Professional Information
occupation: "Chief Executive Officer",
source_of_funds: "Business Income",
// Ownership & Roles
ownership_percentage: 60,
is_director: true,
is_authorized_signatory: true,
// PEP Declaration
is_pep_occupation: false,
is_pep_related: false,
is_pip: false,
},
{
// Second UBO example
email: "[email protected]",
first_name: "John",
last_name: "Doe",
dob: "1980-03-20",
address: "789 Co-founder Street",
postcode: "7800",
city: "Cape Town",
country: "ZA",
nationality: "ZA",
id_number: "8003205800089",
occupation: "Chief Technology Officer",
source_of_funds: "Business Income",
ownership_percentage: 40,
is_director: true,
is_authorized_signatory: false,
is_pep_occupation: false,
is_pep_related: false,
is_pip: false,
},
],
};
const axiosConfig = {
method: method,
url: url,
headers: headers,
data: customerData,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
createBusinessCustomer();
/persona/resource/customers
Description
Create a new business customer account with company details and Ultimate Beneficial Owner (UBO) information. Merchants create customers under their own account. Aggregators can create customers under their own account or under a specific merchant by providing merchant_uid.
Business customers start with 'pending' status and require verification. Business documents must be uploaded separately using the document upload endpoint (see "Upload Business Customer Documents" section below).
Parameters
Account Information:
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| entity_type | formData | Must be 'business' | Yes | string |
| formData | Business email address (unique per merchant) | Yes | string | |
| phone_number | formData | Business phone number in international format | Yes | string |
| account_name | formData | Business account display name (typically company name) | Yes | string |
| merchant_uid | formData | Merchant UID (aggregators only - create under specific merchant) | No | string |
Business Profile (JSON Object):
Core Business Information:
| Field | Description | Required | Schema |
|---|---|---|---|
| company_name | Legal company name | Yes | string |
| registration_number | Company registration number | Yes | string |
| registration_country | ISO country code where company is registered (2-3 chars) | Yes | string |
| industry | Industry classification (e.g., 'Financial Services', 'Technology', 'Healthcare', 'Retail') | Yes | string |
| purpose_of_account | Business purpose for the account | Yes | string |
| expected_monthly_volume_usd | Expected monthly trading volume in USD | Yes | number |
| jurisdictions_of_operation | Array of country codes where business operates | No | array of strings |
| website | Company website URL | No | string |
| employee_count | Number of employees | No | number |
| address | Business street address | No | string |
| city | Business city | No | string |
| postcode | Business postal code | No | string |
| country | Business country ISO code (2 chars) | No | string |
| license_status | License status: 'Licensed', 'Not Licensed', 'Pending' | No | string |
| license_number | License number (required if license_status is 'Licensed') | Conditional | string |
| license_jurisdictions | Array of country codes where licensed (required if license_status is 'Licensed') | Conditional | array of strings |
UBOs Array (Ultimate Beneficial Owners) - At least 1 required:
Each UBO object must include:
Personal Information:
| Field | Description | Required | Schema |
|---|---|---|---|
| UBO email address | Yes | string | |
| first_name | UBO first name | Yes | string |
| last_name | UBO last name | Yes | string |
| phone_number | UBO phone number | No | string |
| dob | Date of birth (YYYY-MM-DD, must be 18+) | Yes | string |
| address | UBO street address | Yes | string |
| postcode | UBO postal code | Yes | string |
| city | UBO city | Yes | string |
| country | UBO country ISO code (2 chars) | Yes | string |
| nationality | UBO nationality ISO code (2 chars) | Yes | string |
| id_number | UBO national ID or passport number | Yes | string |
| occupation | UBO occupation | Yes | string |
| source_of_funds | UBO source of funds | Yes | string |
| ownership_percentage | Ownership percentage (minimum 5%, maximum 100%) | Yes | number |
| is_director | Is the UBO a company director? | No | boolean |
| is_authorized_signatory | Is the UBO an authorized signatory? | No | boolean |
| is_pep_occupation | Is the UBO a PEP by occupation? | No | boolean |
| pep_occupation_data | Details if is_pep_occupation is true | Conditional | string |
| is_pep_related | Is the UBO related to a PEP? | No | boolean |
| pep_related_data | Details if is_pep_related is true | Conditional | string |
| is_pip | Is the UBO a Prominent Influential Person? | No | boolean |
| pip_data | Details if is_pip is true | Conditional | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 201 | Business customer created successfully | Customer |
| 422 | Validation error | Error array |
GET List Customers
// listCustomers.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
listCustomers = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "GET";
const path = `/api/v2/persona/resource/customers?page=1&limit=100`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const axiosConfig = {
method: method,
url: url,
headers: headers,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
listCustomers();
/persona/resource/customers
Description
Get a paginated list of customers. Merchants see only their own customers. Aggregators see all customers (their own + all their merchants' customers). Aggregators can filter by specific merchant using merchant_uid.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| page | query | Page number (defaults to 1) | No | integer |
| limit | query | Number of results per page (default: 100, max: 1000) | No | integer |
| merchant_uid | query | Filter by merchant UID (aggregators only) | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | List of customers with pagination | [ Customer ] |
Response Headers:
Page: 1
Per-Page: 100
Total: 150
Response Body (Array):
[
{
"uid": "CCT1234567",
"account_name": "CUST-001",
"state": "active",
"custom_id": "CUST-001",
"kyc_status": "verified",
"created_at": "2024-01-01T00:00:00.000Z"
},
{
"uid": "CCT1234567",
"account_name": "CUST-002",
"state": "active",
"custom_id": "CUST-002",
"kyc_status": "verified",
"created_at": "2024-01-02T00:00:00.000Z"
}
]
Note: Pagination information is returned in HTTP headers (Page, Per-Page, Total), not in the response body.
GET Get Customer by UID
// getCustomer.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
getCustomer = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "GET";
const customerUid = "CCT1234567";
const path = `/api/v2/persona/resource/customers/${customerUid}`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const axiosConfig = {
method: method,
url: url,
headers: headers,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
getCustomer();
/persona/resource/customers/{uid}
Description
Retrieve a specific customer with full details including profile, phone, labels, and business information (if applicable).
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| uid | path | Customer identifier | Yes | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Customer found | Customer |
| 404 | Not found | Error array |
PUT Update Customer
// updateCustomer.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
updateCustomer = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "PUT";
const customerUid = "CCT1234567";
const path = `/api/v2/persona/resource/customers/${customerUid}`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const updateData = {
custom_id: "CUST-001-UPDATED",
external_verification_url: "https://example.com/verify/updated",
};
const axiosConfig = {
method: method,
url: url,
headers: headers,
data: updateData,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
updateCustomer();
/persona/resource/customers/{uid}
Description
Update customer metadata (custom_id and external_verification_url).
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| uid | path | Customer identifier | Yes | string |
| custom_id | formData | Updated custom identifier | No | string |
| external_verification_url | formData | Updated external verification URL | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Customer updated successfully | Customer |
| 404 | Not found | Error array |
| 422 | Validation error | Error array |
POST Upload Individual Customer Documents
// uploadIndividualDocuments.js
const axios = require("axios");
const FormData = require("form-data");
const fs = require("fs");
const generateHeaders = require("./generateHeaders");
uploadDocuments = async (customerUid) => {
const baseUrl = "https://trade.capecrypto.com";
const path = `/api/v2/verification/customers/documents`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
// Document 1: ID Front
const formData1 = new FormData();
formData1.append("upload", fs.createReadStream("./id_front.jpg"));
formData1.append("doc_type", "Passport");
formData1.append("customer_uid", customerUid);
formData1.append("doc_number", "M12345678");
formData1.append("doc_expire", "2028-12-31");
try {
await axios.post(url, formData1, {
headers: { ...headers, ...formData1.getHeaders() },
});
console.log("ID front uploaded successfully");
} catch (err) {
console.log("Error uploading ID front:", err);
}
// Document 2: ID Back
const formData2 = new FormData();
formData2.append("upload", fs.createReadStream("./id_back.jpg"));
formData2.append("doc_type", "Drivers license back");
formData2.append("customer_uid", customerUid);
formData2.append("doc_number", "DL987654");
formData2.append("doc_expire", "2028-12-31");
try {
await axios.post(url, formData2, {
headers: { ...headers, ...formData2.getHeaders() },
});
console.log("ID back uploaded successfully");
} catch (err) {
console.log("Error uploading ID back:", err);
}
// Document 3: Selfie
const formData3 = new FormData();
formData3.append("upload", fs.createReadStream("./selfie.jpg"));
formData3.append("doc_type", "Selfie");
formData3.append("customer_uid", customerUid);
try {
await axios.post(url, formData3, {
headers: { ...headers, ...formData3.getHeaders() },
});
console.log("Selfie uploaded successfully");
} catch (err) {
console.log("Error uploading selfie:", err);
}
// Document 4: Proof of Address
const formData4 = new FormData();
formData4.append("upload", fs.createReadStream("./proof_of_address.pdf"));
formData4.append("doc_type", "Proof of Address");
formData4.append("customer_uid", customerUid);
try {
await axios.post(url, formData4, {
headers: { ...headers, ...formData4.getHeaders() },
});
console.log("Proof of Address uploaded successfully");
} catch (err) {
console.log("Error uploading proof of address:", err);
}
};
// Upload documents after customer creation
uploadDocuments("CCT1234567");
/api/v2/verification/customers/documents
Description
Upload KYC verification documents for individual customers. Multiple documents are required for complete verification:
- ID document (front side, plus back side for identity_card/driver_license)
- Selfie (holding ID document)
- Proof of Address (utility bill, bank statement, etc.)
Each document must be uploaded as a separate request with FormData.
Maximum file size: 4MB per document.
Note: This endpoint should be called after creating an individual customer using the Create Individual Customer endpoint. Use the customer UID returned from the creation response. All documents must be uploaded seperately.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| upload | formData | The document file (image file: JPG, PNG, PDF) - Max 4MB | Yes | File |
| doc_type | formData | Document type (see Available Document Types below) | Yes | string |
| customer_uid | formData | Customer UID from the customer creation response | Yes | string |
| doc_number | formData | ID document number (optional for selfie) | No | string |
| doc_expire | formData | Document expiry date in YYYY-MM-DD format (optional for selfie) | No | string |
Required Documents
The following documents must be uploaded for individual customers:
- ID Document (Front) - Front image of the identification document
doc_type: Use the document type from customer creation ('Passport', 'Identity card', or 'Driver license')- Include
doc_numberanddoc_expire - Note: Passport is single-sided; Identity card and Driver license require back side as well
- ID Document (Back) - Back image of identification document (Identity card and Driver license only)
doc_type: Add " back" suffix to the document type (e.g., 'Drivers license back', 'Identity card back')- Include
doc_numberanddoc_expire - Not required for Passport
- Selfie - Photograph of customer holding their ID document
doc_type: 'Selfie'doc_numberanddoc_expireare optional
- Proof of Address - Utility bill, bank statement, or lease agreement
doc_type: 'Proof of Address'- Document must be less than 3 months old
doc_numberanddoc_expireare optional
Available Document Types
Valid doc_type values for individual customer KYC documents:
Note: Use Title Case format exactly as shown below (e.g., "Passport", "Proof of Address", "Board Resolution"). The system automatically converts these to internal format.
PassportPassport otherIdentity cardIdentity bookDriver licenseIdentity card frontDriver license frontIdentity card backDriver license backProof of AddressSource of FundsSelfieDrivers licenseDrivers license backGreen Identity Book
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Document uploaded successfully | Success object |
| 400 | Invalid doc_type | Error array |
| 422 | Validation error | Error array |
POST Upload Business Customer Documents
// uploadBusinessDocuments.js
const axios = require("axios");
const FormData = require("form-data");
const fs = require("fs");
const generateHeaders = require("./generateHeaders");
uploadBusinessDocument = async (customerUid, documentType, filePath) => {
const baseUrl = "https://trade.capecrypto.com";
const path = `/api/v2/verification/customers/documents`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const formData = new FormData();
formData.append("upload", fs.createReadStream(filePath));
formData.append("doc_type", documentType);
formData.append("customer_uid", customerUid);
formData.append("doc_number", "N/A");
formData.append("doc_expire", "N/A");
try {
const result = await axios.post(url, formData, {
headers: { ...headers, ...formData.getHeaders() },
});
console.log(`${documentType} uploaded successfully`);
return result.data;
} catch (err) {
console.log(`Error uploading ${documentType}:`, err);
throw err;
}
};
// Upload all required business documents
const uploadAllBusinessDocuments = async (customerUid) => {
const documents = [
{ type: "Board Resolution", file: "./board_resolution.pdf" },
{ type: "Company Registration", file: "./company_registration.pdf" },
{ type: "AML Policy", file: "./aml_policy.pdf" },
{ type: "Business License", file: "./business_license.pdf" },
{ type: "Business Proof of Address", file: "./proof_of_address.pdf" },
{ type: "Shareholder Certificate", file: "./shareholder_certificate.pdf" },
{ type: "Incorporation Certificate", file: "./incorporation_cert.pdf" },
{ type: "Bank Statement", file: "./bank_statement.pdf" },
{ type: "Tax Statement", file: "./tax_statement.pdf" },
];
for (const doc of documents) {
await uploadBusinessDocument(customerUid, doc.type, doc.file);
}
console.log("All business documents uploaded successfully");
};
// Upload documents after business customer creation
uploadAllBusinessDocuments("CCT1234567");
/api/v2/verification/customers/documents
Description
Upload KYB verification documents for business customers. Nine business documents are required for complete verification. Additionally, all UBOs (Ultimate Beneficial Owners) must upload their individual KYC documents (see "Upload UBO Documents" section below). Each document must be uploaded as a separate request with FormData.
Maximum file size: 4MB per document.
Note: This endpoint should be called after creating a business customer using the "Create Business Customer" endpoint. Use the customer UID returned from the creation response. When all 9 business documents and all UBO documents are uploaded, the system will notify administrators for manual review.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| upload | formData | The document file (PDF, JPG, PNG) - Max 10MB | Yes | File |
| doc_type | formData | Business document type (see list below) | Yes | string |
| customer_uid | formData | Customer UID from the business customer creation response | Yes | string |
| doc_number | formData | Use 'N/A' for business documents | No | string |
| doc_expire | formData | Use 'N/A' for business documents | No | string |
Available Business Document Types
Valid doc_type values for business customer KYB documents:
Note: Use Title Case format exactly as shown below (e.g., "Board Resolution", "Company Registration"). The system automatically converts these to internal format.
Board Resolution- Board resolution authorizing account opening, signed by authorized directorsCompany Registration- Official company registration certificate proving legal existenceAML Policy- Anti-Money Laundering policy documentBusiness License- Industry-specific operating license (if applicable)Business Proof of Address- Utility bill or lease agreement (must be less than 3 months old)Shareholder Certificate- Official list of company shareholders showing ownership structureIncorporation Certificate- Certificate of incorporation / Articles of AssociationBank Statement- Recent company bank statement (must be less than 3 months old)Tax Statement- Valid tax compliance certificate proving good standing with tax authorities
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Document uploaded successfully | Success object |
| 400 | Invalid doc_type | Error array |
| 422 | Validation error | Error array |
POST Upload UBO Documents
// uploadUBODocuments.js
const axios = require("axios");
const FormData = require("form-data");
const fs = require("fs");
const generateHeaders = require("./generateHeaders");
uploadUBODocuments = async (uboUid) => {
const baseUrl = "https://trade.capecrypto.com";
const path = `/api/v2/verification/customers/documents`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
// Document 1: ID Front (Passport example)
const formData1 = new FormData();
formData1.append("upload", fs.createReadStream("./ubo_passport.jpg"));
formData1.append("doc_type", "Passport");
formData1.append("customer_uid", uboUid);
formData1.append("doc_number", "M98765432");
formData1.append("doc_expire", "2029-06-30");
try {
await axios.post(url, formData1, {
headers: { ...headers, ...formData1.getHeaders() },
});
console.log("UBO ID uploaded successfully");
} catch (err) {
console.log("Error uploading UBO ID:", err);
}
// Document 2: Selfie
const formData2 = new FormData();
formData2.append("upload", fs.createReadStream("./ubo_selfie.jpg"));
formData2.append("doc_type", "Selfie");
formData2.append("customer_uid", uboUid);
try {
await axios.post(url, formData2, {
headers: { ...headers, ...formData2.getHeaders() },
});
console.log("UBO Selfie uploaded successfully");
} catch (err) {
console.log("Error uploading UBO selfie:", err);
}
// Document 3: Proof of Address
const formData3 = new FormData();
formData3.append("upload", fs.createReadStream("./ubo_proof_of_address.pdf"));
formData3.append("doc_type", "Proof of Address");
formData3.append("customer_uid", uboUid);
try {
await axios.post(url, formData3, {
headers: { ...headers, ...formData3.getHeaders() },
});
console.log("UBO Proof of Address uploaded successfully");
} catch (err) {
console.log("Error uploading UBO proof of address:", err);
}
};
// Upload documents for each UBO
// UBO UIDs are returned when creating the business customer
uploadUBODocuments("ID_UBO_001_ABC");
uploadUBODocuments("ID_UBO_002_XYZ");
/api/v2/verification/customers/documents
Description
Upload KYC verification documents for Ultimate Beneficial Owners (UBOs) of business customers. Each UBO must complete individual KYC verification by uploading their personal documents. This is required in addition to the business entity documents.
When creating a business customer, UBO user accounts are automatically created and their UIDs are returned in the creation response. Use these UBO UIDs as the customer_uid when uploading UBO documents.
Business KYB completion requires:
- All 9 business entity documents uploaded
- All UBOs must have complete KYC documents
Each document must be uploaded as a separate request with FormData.
Maximum file size: 4MB per document.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| upload | formData | The document file (image file: JPG, PNG, PDF) - Max 10MB | Yes | File |
| doc_type | formData | Document type (see Required UBO Documents below) | Yes | string |
| customer_uid | formData | UBO's UID from the business customer creation response | Yes | string |
| doc_number | formData | ID document number (optional for selfie) | No | string |
| doc_expire | formData | Document expiry date in YYYY-MM-DD format | No | string |
Required UBO Documents
Each UBO must upload the following documents:
- ID Document (Front) - Front image of the identification document
doc_type: 'Passport', 'Identity card', or 'Driver license'- Include
doc_numberanddoc_expire - Note: Passport is single-sided; Identity card and Driver license require back side as well
- ID Document (Back) - Back image (Identity card and Driver license only)
doc_type: 'Identity card back' or 'Drivers license back'- Include
doc_numberanddoc_expire - Not required for Passport
- Selfie - Photograph of UBO holding their ID document
doc_type: 'Selfie'doc_numberanddoc_expireare optional
- Proof of Address - Utility bill, bank statement, or lease agreement
doc_type: 'Proof of Address'- Document must be less than 3 months old
doc_numberanddoc_expireare optional
Available Document Types for UBOs
Valid doc_type values for UBO KYC documents (same as individual customers):
PassportPassport otherIdentity cardIdentity card frontIdentity card backIdentity bookGreen Identity BookDriver licenseDriver license frontDrivers licenseDrivers license backDriver license backSelfieLivenessProof of AddressSource of Funds
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Document uploaded successfully | Success object |
| 400 | Invalid doc_type | Error array |
| 422 | Validation error | Error array |
Business KYB Completion
When all requirements are met:
- All 9 business documents uploaded ✓
- All UBOs have uploaded their complete KYC documents (4-5 documents each) ✓
The system will:
- Automatically notify administrators via Slack for manual review
- Business customer remains in "pending" status until admin approval
- No automatic verification for business customers (always requires manual review)
POST Create payment reference
// createPaymentReference.js
const axios = require("axios");
const url = "https://trade.capecrypto.com/api/v2/atlas/account/payments";
const createPaymentReference = async () => {
const headers = {
"Content-Type": "application/json",
"X-Auth-Apikey": "your_api_key",
"X-Auth-Nonce": Date.now(),
"X-Auth-Signature": "your_generated_signature",
};
const paymentData = {
uid: "ID123456789",
currency: "zar",
expected_amount: "100"
deposit_action: {
action: "aggregate_convert_and_withdraw",
target_currency: "zar",
beneficiary_id: 456,
max_slippage: 0.5,
},
webhook_url: "https://merchant.com/webhooks/deposits",
webhook_secret: "my_secret_key",
webhook_protocol: "hmac",
};
const axiosConfig = {
method: "post",
url: url,
headers: headers,
data: paymentData,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
createPaymentReference();
/api/v2/account/payments
Description
Create a new payment reference for a customer account. The payment reference provides a unique deposit reference code that the customer can use when making deposits. Optionally configure automated actions (aggregate, convert, withdraw) that execute when deposits are received. You can optionally provide a webhook url, with optional secret and protocol that will be sent as a header with the webhook notification.
Access: Merchants and Aggregators only
Webhook Notifications: When the configured action completes successfully, a webhook notification will be sent to the provided webhook URL (optional).
When the configured action completes successfully, a webhook notification will be sent to the provided webhook URL (optional).
Request Details
- HTTP Method:
POST - Content-Type:
application/json
Authentication / Protocol Modes (webhook_protocol)
| webhook_protocol | Behavior | Header X-Auth-Webhook contains |
|---|---|---|
hmac (recommended) |
The raw request body is signed with HMAC-SHA256 using your webhook_secret. The signature is base64-encoded. |
Base64(HMAC-SHA256(body, webhook_secret)) |
plain_text |
The secret itself is sent in clear text (not recommended for production) | The raw webhook_secret value |
| not set (default) | No authentication header is added | Header is omitted |
Successful Response Requirement
Your endpoint must return an HTTP 2xx status code (e.g., 200, 201, 204) as quickly as possible.
- Any non-2xx response or timeout will trigger retries.
- Retry schedule (exponential backoff):
| Attempt | Delay | Notes |
|---|---|---|
| 1 | 30 seconds | Initial retry after first failure |
| 2 | 60 seconds | |
| 3 | 5 minutes | |
| 4 | 10 minutes | |
| 5 | 30 minutes | |
| 6 | 1 hour | |
| 7 | 2 hours | Final retry attempt |
Recommendations
- Use
hmacmode in production. - Verify the
X-Auth-Webhooksignature on your side by recomputing HMAC-SHA256 of the raw request body with your secret and comparing it to the header value. - Respond immediately with
200 OK(you can validate the signature asynchronously if needed).
Webhook Verification
When you receive a webhook notification, you should verify its authenticity before processing.
Verifying HMAC Signatures (Recommended)
For webhook_protocol: "hmac", the payload is HMAC signed and BASE64 encoded (BASE64(HMAC(payload, secret))), and then sent. Validate the sent HMAC signature by repeating what was done by Cape Crypto: HMAC sign the received body with your secret, then encode the resulting HMAC signature to BASE64, and finally compare the received BASE64 encoded signture with your calculated BASE64 encoded signature.
Warning: Plain text mode is not recommended for production as the secret is transmitted in clear text.
No Authentication Mode
For webhook_protocol: "none" or when not set, no X-Auth-Webhook header is sent. Consider implementing additional security measures like IP whitelisting or using webhook signing URLs.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| uid | body | Customer sub-account UID | Yes | string |
| currency | body | Currency code (e.g., "btc", "zar") | Yes | string |
| expected_amount | body | Expected amount the customer will deposit (e.g., "1000") | No | string |
| deposit_action | body | Automated action configuration (see Deposit Action Object below) | No | object |
| webhook_url | body | Webhook URL for notifications | No | string |
| webhook_secret | body | Webhook secret for HMAC signing | No | string |
| webhook_protocol | body | Webhook protocol: "hmac", "plain_text", or "none" (default: "none") | No | string |
Deposit Action Object:
| Field | Description | Required | Schema |
|---|---|---|---|
| action | Action type: "none", "aggregate", "convert_and_withdraw", "aggregate_convert_and_withdraw" | Yes | string |
| target_currency | Target currency for conversion (required if action includes conversion) | Conditional | string |
| beneficiary_id | Beneficiary ID for withdrawal (required if action includes withdrawal) | Conditional | integer |
| lightning_invoice | Lightning invoice for withdrawal (alternative to beneficiary_id) | No | string |
| max_slippage | Maximum slippage percentage for conversion (default: 1.0, max: 5.0) | No | decimal |
Deposit Action Types:
- none: No automated action, deposit sits in account
- aggregate: Combine multiple small deposits into one before processing
- convert_and_withdraw: Convert currency and withdraw to beneficiary
- aggregate_convert_and_withdraw: Aggregate deposits, then convert and withdraw
Responses
| Code | Description | Schema |
|---|---|---|
| 201 | Payment reference created | PaymentReference |
| 403 | Forbidden (not merchant/aggregator) | Error array |
| 422 | Validation error | Error array |
Example Response:
API Response - Payment Reference Created:
// Request Body examples - POST /api/v2/account/payments
{
"id": 123,
"reference": "PAY-ABC123",
"uid": "ID123456789",
"currency": "btc",
"deposit_action": {
"action": "aggregate_convert_and_withdraw",
"target_currency": "zar",
"beneficiary_id": 456,
"lightning_invoice": null,
"max_slippage": 0.5
},
"state": "pending_deposit",
"webhook_url": "https://merchant.com/webhooks/deposits",
"webhook_protocol": "hmac",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
Additional Request Examples
Example: Simple Deposit (No Action)
{
"uid": "ID123456789",
"currency": "btc"
}
Example: Aggregate Only
{
"uid": "ID123456789",
"currency": "btc",
"deposit_action": {
"action": "aggregate"
}
}
Example: Convert and Withdraw (Lightning)
{
"uid": "ID123456789",
"currency": "btc",
"deposit_action": {
"action": "convert_and_withdraw",
"target_currency": "btc",
"lightning_invoice": "lnbc100n1...",
"max_slippage": 1.0
},
"webhook_url": "https://merchant.com/webhooks/deposits",
"webhook_secret": "my_secret_key",
"webhook_protocol": "hmac"
}
Webhook Payload Examples
When a payment reference progresses through its lifecycle, webhook notifications are sent to your configured webhook_url with different event payloads. Below are the four event types and their payload structures.
Event 1: Deposit Matched
Event Name: payment_reference.deposit_matched
When Triggered: When a deposit is successfully linked to the payment reference
Payload:
// Webhook Payload - POST to your webhook_url
{
"event": "payment_reference.deposit_matched",
"payment_reference_id": 123,
"reference": "PAY-ABC123",
"uid": "ID123456789",
"deposit_id": 456,
"amount": "100.00",
"currency": "usd",
"deposit_blockchain": "ethereum-erc20",
"txid": "0xabc123def456...",
"invoice": "lnbc1000u1...",
"created_at": "2025-01-17T10:00:00.000Z"
}
Field Notes:
invoice: Only present for Lightning depositsdeposit_blockchain: Blockchain key (e.g., "bitcoin", "ethereum-erc20", "tron-trc20")
Event 2: Completed
Event Name: payment_reference.completed
When Triggered: When all configured actions (aggregate, convert, withdraw) complete successfully
Payload:
// Webhook Payload - POST to your webhook_url
{
"event": "payment_reference.completed",
"payment_reference_id": 123,
"reference": "PAY-ABC123",
"uid": "ID123456789",
"deposit_id": 456,
"internal_transfer_id": 202,
"order_id": 789,
"withdraw_id": 101,
"deposit_blockchain": "ethereum-erc20",
"invoice": "lnbc1000u1...",
"withdraw_blockchain": "bitcoin",
"created_at": "2025-01-17T10:05:00.000Z"
}
Field Notes (Conditional):
internal_transfer_id: Only present ifdeposit_actionwasaggregateoraggregate_convert_and_withdraworder_id: Only present ifdeposit_actionincluded conversion (convert_and_withdraworaggregate_convert_and_withdraw)withdraw_id: Only present ifdeposit_actionincluded withdrawalinvoice: Only present for Lightning depositswithdraw_blockchain: Only present if withdrawal occurred
Event 3: Failed
Event Name: payment_reference.failed
When Triggered: When processing encounters a permanent failure
Payload:
// Webhook Payload - POST to your webhook_url
{
"event": "payment_reference.failed",
"payment_reference_id": 123,
"reference": "PAY-ABC123",
"uid": "ID123456789",
"deposit_id": 456,
"error": "Insufficient liquidity for market order on btcusd",
"deposit_blockchain": "ethereum-erc20",
"invoice": "lnbc1000u1...",
"created_at": "2025-01-17T10:10:00.000Z"
}
Field Notes:
error: Detailed error message describing the failure reasoninvoice: Only present for Lightning deposits
Event 4: Duplicate Deposit
Event Name: payment_reference.duplicate_deposit
When Triggered: When a second deposit is received for a payment reference that already has a deposit linked
Payload:
// Webhook Payload - POST to your webhook_url
{
"event": "payment_reference.duplicate_deposit",
"reference": "PAY-ABC123",
"member_uid": "ID123456789",
"currency_id": "usd",
"amount": "100.00",
"original_deposit_id": 456,
"duplicate_deposit_id": 789,
"duplicate_count": 1,
"timestamp": "2025-01-17T10:15:00.000Z"
}
Field Notes:
duplicate_count: Number of duplicate deposits received (increments with each duplicate)- This event helps you track and handle unexpected duplicate payments from customers
GET List payment references
// listPaymentReferences.js
const axios = require("axios");
const url = "https://trade.capecrypto.com/api/v2/atlas/account/payments";
const listPaymentReferences = async () => {
const headers = {
"X-Auth-Apikey": "your_api_key",
"X-Auth-Nonce": Date.now(),
"X-Auth-Signature": "your_generated_signature",
};
const axiosConfig = {
method: "get",
url: url,
headers: headers,
params: {
page: 1,
limit: 50,
},
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
listPaymentReferences();
/api/v2/account/payments
Description
List all payment references for your customer accounts. Returns paginated results.
Access: Merchants and Aggregators only
Acting as a Customer: Provide uid parameter to list payment references for that customer sub-account. The customer must belong to your merchant account.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| uid | query | Customer sub-account UID | No | string |
| page | query | Page number (default: 1) | No | integer |
| limit | query | Results per page (max: 100) | No | integer |
Responses
Headers:
| Header | Description |
|---|---|
| Total | Total number of records |
| Page | Current page number |
| Per-Page | Records returned per page |
| Code | Description | Schema |
|---|---|---|
| 200 | Payment references retrieved | Array of PaymentReference |
| 403 | Forbidden (not merchant/aggregator) | Error array |
| 422 | Validation error | Error array |
Example Response:
[
{
"id": 123,
"reference": "PAY-ABC123",
"uid": "ID123456789",
"currency": "btc",
"deposit_action": {
"action": "convert_and_withdraw",
"target_currency": "zar",
"beneficiary_id": 456,
"max_slippage": 0.5
},
"state": "pending_deposit",
"webhook_url": "https://merchant.com/webhooks/deposits",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
]
GET Get payment reference by id
// showPaymentReference.js
const axios = require("axios");
const paymentId = 123;
const url = `https://trade.capecrypto.com/api/v2/atlas/account/payments/${paymentId}`;
const showPaymentReference = async () => {
const headers = {
"X-Auth-Apikey": "your_api_key",
"X-Auth-Nonce": Date.now(),
"X-Auth-Signature": "your_generated_signature",
};
const axiosConfig = {
method: "get",
url: url,
headers: headers,
params: {
includeSecret: true, // Optional: include webhook secret
},
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
showPaymentReference();
/api/v2/account/payments/:id
Description
Retrieve details of a specific payment reference.
Access: Merchants and Aggregators only
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| id | path | Payment reference ID | Yes | integer |
| includeSecret | query | Include webhook secret in response (default: false) | No | boolean |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Payment reference retrieved | PaymentReference |
| 403 | Forbidden (not merchant/aggregator) | Error array |
| 404 | Payment reference not found | Error array |
Example Response (with includeSecret=true):
{
"id": 123,
"reference": "PAY-ABC123",
"uid": "ID123456789",
"currency": "btc",
"deposit_action": {
"action": "aggregate_convert_and_withdraw",
"target_currency": "zar",
"beneficiary_id": 456,
"lightning_invoice": null,
"max_slippage": 0.5
},
"state": "completed",
"webhook_url": "https://merchant.com/webhooks/deposits",
"webhook_secret": "wh_secret_abc123xyz",
"webhook_protocol": "hmac",
"deposit_id": 789,
"order_id": 1011,
"withdraw_id": 1213,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T14:45:00Z"
}
Merchant Management (Aggregators Only)
Note: These endpoints are only available for Aggregators to manage their merchant accounts.
GET List Merchants
// listMerchants.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
listMerchants = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "GET";
const path = `/api/v2/persona/resource/merchants?page=1&limit=100`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const axiosConfig = {
method: method,
url: url,
headers: headers,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
listMerchants();
/persona/resource/merchants
Description
Get a paginated list of merchant accounts managed by the aggregator.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| page | query | Page number (defaults to 1) | No | integer |
| limit | query | Number of results per page (default: 100, max: 1000) | No | integer |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | List of merchants with pagination | Array of merchants (see Customer (Business)) |
Note: Pagination information is returned in HTTP headers (Page, Per-Page, Total), not in the response body.
GET List Merchant's Customers
// listMerchantCustomers.js
const axios = require("axios");
const generateHeaders = require("./generateHeaders");
listMerchantCustomers = async () => {
const baseUrl = "https://trade.capecrypto.com";
const method = "GET";
const merchantUid = "CCT1234567";
const path = `/api/v2/persona/resource/merchants/${merchantUid}/customers?page=1&limit=100`;
const url = `${baseUrl}${path}`;
const headers = await generateHeaders();
const axiosConfig = {
method: method,
url: url,
headers: headers,
};
try {
const results = await axios(axiosConfig);
console.log(JSON.stringify(results.data, undefined, 2));
} catch (err) {
console.log(err);
}
};
listMerchantCustomers();
/persona/resource/merchants/{uid}/customers
Description
Get a paginated list of customers belonging to a specific merchant. This provides a nested view for aggregators to see which customers belong to which merchant.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| uid | path | Merchant identifier | Yes | string |
| page | query | Page number (defaults to 1) | No | integer |
| limit | query | Number of results per page (default: 100, max: 1000) | No | integer |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | List of merchant's customers with pagination | [ Customer ] |
| 404 | Merchant not found | Error array |
Note: Pagination information is returned in HTTP headers (Page, Per-Page, Total), not in the response body.
Trading
POST Create Order
/market/orders
Description
Submits a Buy/Sell order to the order book. Orders should be confirmed that they have been created either by querying the order ID that is returned in the response, or subscribing to the websockets for order updates.
You can supply your own custom_order_id for tracking purposes for you own system.
For limit orders, you can specify post_only to ensure that orders wont complete if they would have matched. The order request will fail if post_only is true, and the order would have matched.
Parameters
| Name | Description | Required | Schema |
|---|---|---|---|
| market | The market name eg 'btczar' | Yes | string |
| side | The direction of the order eg buy | Yes | string |
| volume | The order size in base currency | Yes | string |
| ord_type | Limit / Market | Yes | string |
| price | The limit order price | No | string |
| post_only | Fail limit order if matched | No | boolean |
| custom_order_id | Your custom order id | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 201 | Order submitted | Order |
GET Order by id
/market/orders/{id}
Description
Get single order details.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| id | path | Yes | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Order details. | Order |
GET All orders
/market/orders
Description
Get orders, result is paginated.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| market | query | No | string | |
| base_unit | query | No | string | |
| quote_unit | query | No | string | |
| state | query | Filter order by state. | No | string |
| limit | query | Limit the number of returned orders, default to 100. | No | integer |
| page | query | Paginated page number. | No | integer |
| order_by | query | If set, returned orders will be sorted in specific order, default to "desc". | No | string |
| ord_type | query | Filter order by ord_type. | No | string |
| type | query | Filter order by type. | No | string |
| time_from | query | Timestamp in ms.If set, only orders created after the time will be returned. | No | integer |
| time_to | query | Timestamp in ms.If set, only orders created before the time will be returned. | No | integer |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get your orders, result is paginated. | [ Order ] |
GET Trades
/market/trades
Description
Fetch your trades executed on your orders.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| market | query | No | string | |
| order_id | query | Get the trades for a specific order_id | No | integer |
| limit | query | Limit the number of returned trades. Default to 100. | No | integer |
| page | query | Paginated page number. | No | integer |
| time_from | query | Timestamp in ms. Return trades executed after the timestamp. | No | integer |
| time_to | query | Timestamp in ms. Return trades executed after the timestamp. | No | integer |
| order_by | query | Order by created_at, defaults to 'desc'. [asc desc] | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 200 | Get your executed trades.. | [ Trade ] |
POST Cancel Order
/market/orders/{id}/cancel
Description
Cancel a specific order by Order id.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| id | path | Yes | string |
Responses
| Code | Description |
|---|---|
| 201 | Cancel an order. |
POST Cancel All Orders
/market/orders/cancel
Description
Cancel all of your open orders.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| market | formData | No | string | |
| side | formData | Only cancel orders on this side [buy sell] | No | string |
Responses
| Code | Description | Schema |
|---|---|---|
| 201 | Cancel all my orders. | Order |
Websocket
// generateHeaders.js
const crypto = require("crypto");
const apiKey = "123456...";
const secret = "ABCDEF...";
module.exports = async () => {
const timeStamp = new Date().getTime();
const queryString = `${timeStamp}${apikey}`;
const signature = crypto
.createHmac("sha256", secret)
.update(queryString)
.digest("hex");
return {
"Content-Type": "application/json;charset=utf-8",
"X-Auth-Apikey": apikey,
"X-Auth-Nonce": timeStamp,
"X-Auth-Signature": signature,
};
};
The high performance websocket provides near realtime updates on the orderbook, your orders and balances.
The orderbook endpoint does not require authenticaiton, while the private endpoints (orders, trades, and balances) do require authentication.
To connect to the private endpoints on the websocket, send the authentication headers with the connection request, similar to the REST authentication, and subscribe to the streams you are interested in. You can combine multiple subscriptions in one request, and also provide the subscriptions at the same time as connecting - ie. you only need 1x request to get fully connected.
You can also combine the public and private streams into one so that you only need to maintain 1x connection for example ?stream=btczar.ob-inc&stream=order&stream=trade&stream=balance
SUBSCRIBE Orderbook
const WebSocket = require("ws");
async function connectWssPublic() {
try {
const baseUrl = `wss://trade.capecrypto.com/api/v2`;
const streams = "?stream=btczar.ob-inc&stream=ethzar.ob-inc";
const path = `/websocket/public${streams}`;
ws = new WebSocket(`${baseUrl}${path}`);
} catch (err) {
console.log(err);
}
ws.on("open", () => {
console.log("Connected to websocket");
});
ws.on("message", function message(data) {
data = JSON.parse(data);
console.log(JSON.stringify(data, undefined, 2));
if (["ob-snap", "ob-inc"].includes(data)) {
// handle all subscribed markets
// updateOrderBook(data);
}
});
}
connectWssPublic();
?stream={market}.ob-inc
Description
Subscribe to orderbook updates for the specified market. You can subscribe to multiple markets at the same time by adding each market as a param, eg
?stream=btczar.ob-incðzar.ob-inc
Note: if the amount is empty "", the order has been removed from the orderbook
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| {market}-ob.inc | query | Name of the market to subscribe to | yes | string |
Responses
| Description | Schema |
|---|---|
| snapshot | [ Snapshot ] |
| increment | [ Increment ] |
SUBSCRIBE K-Line
const WebSocket = require("ws");
async function connectWssPublic() {
try {
const baseUrl = `wss://trade.capecrypto.com/api/v2`;
const streams = "?stream=btczar.kline-15m";
const path = `/websocket/public${streams}`;
ws = new WebSocket(`${baseUrl}${path}`);
} catch (err) {
console.log(err);
}
ws.on("open", () => {
console.log("Connected to websocket");
});
ws.on("message", function message(data) {
data = JSON.parse(data);
console.log(JSON.stringify(data, undefined, 2));
if (["kline-"].includes(data)) {
// handle all subscribed markets
// updateOrderBook(data);
}
});
}
connectWssPublic();
?stream={market}.kline-{period}
Description
Subscribe to the K-LINE (OHLC) for the specified market to get updates on the OHLC for each period. Similary to the otherendpoints, you can combine streams into a single subscription request.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| {market} | query | Name of the market to subscribe to | yes | string |
| {period} | query | The period to get updates at "1m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "12h", "1d", "3d", "1w" | yes | string |
Responses
| Name | Type | Description |
|---|---|---|
| {market}.kline-{period} | object | Key name of the OHLC for the market and period |
| OHLC for that period | [number] | Array of Timestamp, Open, High, Low, Close, Volume |
SUBSCRIBE Private
async function connectWssPrivate() {
try {
const baseUrl = `wss://trade.capecrypto.com/api/v2`;
const headers = await generateHeaders();
const streams =
"?stream=btczar.ob-inc&stream=ethzar.ob-inc&stream=order&stream=trade&stream=balance";
const path = `/websocket/private${streams}`;
ws = new WebSocket(`${baseUrl}${path}`, { headers });
} catch (err) {
console.log(err);
}
ws.on("open", () => {
console.log("Connected to websocket");
});
ws.on("message", (data) => {
data = JSON.parse(data);
console.log(JSON.stringify(data, undefined, 2));
if (data["xrpzar.ob-snap"] || data["xrpzar.ob-inc"]) {
// handle orderbook updates for subscribed markets
// updateOrderBook(data);
}
if (data["order"]) {
// handleOwnOrderUpdate(data);
}
if (data["trade"]) {
// handleOwnTradeUpdate(data);
}
if (data["balance"]) {
// handleBalanceUpdate(data);
}
});
}
connectWssPrivate();
Subscribe to your account and receive real-time updates on your orders, trades, and balances.
Important: You can subscribe to ALL streams (both public and private) in a single WebSocket connection using the /websocket/private endpoint. This is the recommended approach as it minimizes connection overhead and provides a unified stream of all market events.
Private streams only:
?stream=order&stream=trade&stream=balance
Combining public orderbook + private streams (recommended):
?stream=btczar.ob-inc&stream=order&stream=trade&stream=balance
Benefits of combining streams:
- Single WebSocket connection for all updates
- Synchronized view of orderbook changes and your order executions
- Real-time balance updates as trades execute
- Reduced latency and connection overhead
Order Stream
Subscription: ?stream=order
Receive real-time updates when your orders are created, updated, filled, or canceled.
Response Format:
{
"order": {
"id": 173757,
"uuid": "7e86a4fa-fcae-4953-b9bc-d6f328beea48",
"side": "sell",
"ord_type": "limit",
"price": "800000.0",
"avg_price": "0.0",
"state": "wait",
"market": "btczar",
"created_at": "2023-12-08T08:46:59+01:00",
"updated_at": "2023-12-08T08:46:59+01:00",
"origin_volume": "0.00001",
"remaining_volume": "0.00001",
"executed_volume": "0.0",
"trades_count": 0
}
}
Order Update Triggers:
- Order created and accepted
- Order partially filled
- Order fully filled (state changes to "done")
- Order canceled
- Order rejected
Trade Stream
Subscription: ?stream=trade
Receive real-time updates when your orders are matched and trades are executed.
Response Format:
{
"trade": {
"id": 12345,
"order_id": 173757,
"price": "800000.0",
"amount": "0.00001",
"total": "8.0",
"market": "btczar",
"side": "sell",
"taker_type": "buy",
"fee_currency": "zar",
"fee": "0.002",
"fee_amount": "0.016",
"created_at": "2023-12-08T08:47:01+01:00"
}
}
Trade Event Triggers:
- Your limit order is matched (you are the maker)
- Your market order is executed (you are the taker)
- Your order is partially filled
Balance Stream
Subscription: ?stream=balance
Receive real-time updates when your account balances change. Balance updates are triggered by trading activity, order placement, and order cancellation.
Response Format:
{
"balance": {
"currency": "btc",
"balance": "0.05000000",
"locked": "0.00001000",
"available": "0.04999000",
"updated_at": 1702027621000
}
}
Response Fields:
| Field | Type | Description |
|---|---|---|
| currency | string | Currency code (btc, eth, zar, usdt, etc.) |
| balance | string | Total balance (available + locked). String for decimal precision |
| locked | string | Amount currently locked in open orders |
| available | string | Available balance for trading (balance - locked) |
| updated_at | integer | Unix timestamp in milliseconds when balance was updated |
Balance Update Triggers:
- Order Creation - Locks balance when a limit or market order is placed
- Order Cancellation - Unlocks balance when an order is canceled
- Trade Execution (Maker) - Updates both base and quote currency balances
- Trade Execution (Taker) - Updates both base and quote currency balances with fee deduction
- Partial Fill - Each partial fill triggers separate balance updates
- Order Rejection - Balance unlock if order validation fails after initial lock
Example 1: Order Creation (Balance Lock)
When you place a buy order for 0.001 BTC @ 800,000 ZAR, your ZAR balance is locked:
{
"balance": {
"currency": "zar",
"balance": "10000.00",
"locked": "800.00",
"available": "9200.00",
"updated_at": 1702027621000
}
}
Example 2: Order Cancellation (Balance Unlock)
When you cancel the order, the locked ZAR is released:
{
"balance": {
"currency": "zar",
"balance": "10000.00",
"locked": "0.00",
"available": "10000.00",
"updated_at": 1702027625000
}
}
Example 3: Trade Execution - Sell Order (Base Currency)
When your sell order for 0.00001 BTC executes, you receive two balance updates. First, the BTC is deducted:
{
"balance": {
"currency": "btc",
"balance": "0.04999000",
"locked": "0.00000000",
"available": "0.04999000",
"updated_at": 1702027630000
}
}
Example 4: Trade Execution - Sell Order (Quote Currency)
Immediately after, you receive ZAR (minus the taker fee of 0.2%):
{
"balance": {
"currency": "zar",
"balance": "10007.984",
"locked": "0.00",
"available": "10007.984",
"updated_at": 1702027630001
}
}
Example 5: Partial Fill
When your order is partially filled (50% of 0.001 BTC order):
{
"balance": {
"currency": "btc",
"balance": "0.04999500",
"locked": "0.00000500",
"available": "0.04999000",
"updated_at": 1702027635000
}
}
Important Notes:
- Atomic Updates: Balance updates are atomic - you receive one update per currency per event
- Dual Updates for Trades: For each trade, expect two balance updates - one for the base currency and one for the quote currency
- Timestamp Precision: The
updated_atfield uses Unix milliseconds (not seconds) - String Values: Balance values are strings to preserve decimal precision - parse them as decimals, not floats
- Rapid Updates: Multiple rapid trades may result in several balance updates in quick succession
- Fee Inclusion: Fees are already deducted from the amounts shown in balance updates
Edge Cases:
- Insufficient Funds: No balance update is sent (order is rejected before locking funds)
- Self-Trade Prevention: If a self-trade is prevented, balance may lock and then immediately unlock
- Partial Fills: Each partial fill of an order triggers separate balance updates for both currencies
- Maker vs Taker Fees: Fee amounts differ based on whether you're the maker (0.1%) or taker (0.2%)
Integration Example:
ws.on("message", (data) => {
data = JSON.parse(data);
if (data["balance"]) {
const { currency, balance, locked, available, updated_at } = data.balance;
console.log(`Balance Update: ${currency}`);
console.log(` Total: ${balance}`);
console.log(` Locked: ${locked}`);
console.log(` Available: ${available}`);
console.log(` Updated: ${new Date(updated_at).toISOString()}`);
// Update your UI
updateBalanceDisplay(currency, available, locked);
}
if (data["order"]) {
// Handle order updates
handleOrderUpdate(data.order);
}
if (data["trade"]) {
// Handle trade updates - expect balance updates to follow immediately
handleTradeUpdate(data.trade);
}
});
Models
Pagination
All paginated list endpoints return an array of items in the response body, with pagination information provided in HTTP response headers:
| Header | Description | Example |
|---|---|---|
| Page | Current page number | 1 |
| Per-Page | Number of items per page | 100 |
| Total | Total number of items across all pages | 150 |
Ticker
| Name | Type | Description |
|---|---|---|
| at | integer | Timestamp of ticker |
| ticker | TickerEntry | Ticker entry for specified time |
TickerEntry
| Name | Type | Description |
|---|---|---|
| low | double | The lowest trade price during last 24 hours (0.0 if no trades executed during last 24 hours) |
| high | double | The highest trade price during last 24 hours (0.0 if no trades executed during last 24 hours) |
| open | double | Price of the first trade executed 24 hours ago or less |
| last | double | The last executed trade price |
| volume | double | Total volume of trades executed during last 24 hours |
| amount | double | Total amount of trades executed during last 24 hours |
| vol | double | Alias to volume |
| avg_price | double | Average price more precisely VWAP (volume weighted average price) |
| price_change_percent | string | Price change over the last 24 hours |
| at | integer | Timestamp of ticker |
Orderbook entry
| Name | Type | Description |
|---|---|---|
| price | number | The price of the order |
| amount | number | The amount of the order at that price. Note: if the amount is empty "", the order has been removed from the orderbook |
Trade
| Name | Type | Description |
|---|---|---|
| id | string | Trade ID. |
| price | double | Trade price. |
| amount | double | Trade amount. |
| total | double | Trade total (Amount * Price). |
| fee_currency | double | Currency user's fees were charged in. |
| fee | double | Percentage of fee user was charged for performed trade. |
| fee_amount | double | Amount of fee user was charged for performed trade. |
| market | string | Trade market id. |
| created_at | string | Trade create time in iso8601 format. |
| taker_type | string | Trade taker order type (sell or buy). |
| side | string | Trade side. |
| order_id | integer | Order id. |
| custom_order_id | integer | Optional custom Order id if supplied. |
Order
| Name | Type | Description |
|---|---|---|
| id | integer | Unique order id. |
| custom_order_id | string | Custom order id if supplied during order creation. |
| side | string | Either 'sell' or 'buy'. |
| ord_type | string | Type of order, either 'limit' or 'market'. |
| post_only | boolean | If the Limit order was submitted as post_only . |
| price | double | Price for 1x base unit. |
| avg_price | double | Average execution price over all executed trades. |
| state | string | Current state of the order Order states |
| market | string | The market in which the order is placed, e.g. 'btczar'. |
| created_at | string | Order create time in iso8601 format. |
| updated_at | string | Order updated time in iso8601 format. |
| origin_volume | double | The original amount of the order. |
| remaining_volume | double | The remaining amount of the original order volume. |
| executed_volume | double | The executed volume of the original order volume. |
| trades_count | integer | Number of trades executed against this limit order. |
| trades | [Trade] | Array of trades executed against this order. |
Market
| Name | Type | Description |
|---|---|---|
| id | string | Unique market id eg 'btczar' |
| name | string | Market name. |
| base_unit | string | Market Base unit. |
| quote_unit | string | Market Quote unit. |
| min_price | double | Minimum order price. |
| max_price | double | Maximum order price. |
| min_amount | double | Minimum order amount. |
| amount_precision | double | Precision for order amount. |
| price_precision | double | Precision for order price. |
| state | string | If the market is enabled for trading or not. |
Currency
| Name | Type | Description |
|---|---|---|
| id | string | Currency code. |
| name | string | Currency name |
| symbol | string | Currency symbol |
| explorer_transaction | string | Currency transaction exprorer url template |
| explorer_address | string | Currency address exprorer url template |
| type | string | Currency type |
| deposit_enabled | string | Currency deposit possibility status (true/false). |
| withdrawal_enabled | string | Currency withdrawal possibility status (true/false). |
| deposit_fee | string | Currency deposit fee |
| min_deposit_amount | string | Minimal deposit amount |
| withdraw_fee | string | Currency withdraw fee |
| min_withdraw_amount | string | Minimal withdraw amount |
| withdraw_limit_24h | string | Currency 24h withdraw limit |
| withdraw_limit_72h | string | Currency 72h withdraw limit |
| base_factor | string | Currency base factor |
| precision | string | Currency precision |
| position | string | Position used for defining currencies order |
| icon_url | string | Currency icon |
| min_confirmations | string | Number of confirmations required for confirming deposit or withdrawal |
| layer_two_withdraw_fee | string | (Layer two dependant) The base layer two withdraw fee |
| layer_two_max_deposit_amount | string | (Layer two dependant) The maximum deposit amount |
| layer_two_max_withdraw_amount | string | (Layer two dependant) The maxumum withdrawal amount |
Withdraw
| Name | Type | Description |
|---|---|---|
| id | integer | The withdrawal id. |
| currency | string | The currency code. |
| type | string | The withdrawal type |
| amount | string | The withdrawal amount |
| fee | double | The exchange fee. |
| blockchain_txid | string | The withdrawal transaction id. |
| rid | string | The beneficiary ID or wallet address on the Blockchain. |
| state | string | The withdrawal state. |
| confirmations | integer | Number of confirmations. |
| note | string | Optional withdraw note. |
| created_at | string | The datetime for the withdrawal. |
| updated_at | string | The datetime for the withdrawal. |
| done_at | string | The datetime when withdraw was completed |
Withdraw quote
| Name | Type | Description |
|---|---|---|
| id | string | The id of the quote. Submit this with your withdrawal request |
| currency | string | The currency code. |
| fee | string | The fee for the withdrawal |
| blockchain | string | The blockchain for this quote |
| created_at | string | The datetime the quote was created. |
| expires_at | string | The datetime the quote expires. |
Beneficiary
| Name | Type | Description |
|---|---|---|
| uuid | integer | Beneficiary Identifier |
| currency | string | Beneficiary currency code. |
| name | string | Name of the beneficiary. |
| description | string | A personal description of the beneficiary for your records. |
| data | json | Bank Account details for FIAT Beneficiary in JSON format. For crypto it's blockchain address and or tag, as well as the travel rule information |
| state | string | Pending, Active, Archived (0,1,2) |
SubAccount
| Name | Type | Description |
|---|---|---|
| uid | string | Sub-account unique identifier |
| account_name | string | Display name for the sub-account |
| state | string | Account state (active, deleted) |
| deposit_action | string | Action for deposits: 'none' (keep in sub-account) or 'transfer_to_parent' (auto-transfer) |
| created_at | string | Timestamp when the sub-account was created in ISO 8601 format |
Customer
Customer accounts come in two types with different response structures:
- Individual Customers - See Customer (Individual) for personal account structure
- Business Customers - See Customer (Business) for business account structure
Customer (Individual)
| Name | Type | Description |
|---|---|---|
| uid | string | Customer unique identifier |
| account_name | string | Display name for the customer (usually custom_id) |
| state | string | Account state (active, deleted) |
| custom_id | string | Custom identifier set by merchant/aggregator |
| external_verification_url | string | URL for external verification system |
| kyc_status | string | KYC verification status (verified, pending, rejected) |
| created_at | string | Timestamp when customer was created in ISO 8601 format |
| profile | object | Customer profile information (first_name, last_name, dob, address, etc.) |
| phone | object | Phone information (country, number, validated_at) |
Customer (Business)
| Name | Type | Description |
|---|---|---|
| id | integer | Customer database ID |
| uid | string | Customer unique identifier |
| string | Business email address | |
| role | string | Account role (customer) |
| entity_type | string | Entity type (business) |
| level | integer | Account level |
| state | string | Account state (active, pending, deleted) |
| custom_id | string | Custom identifier set by merchant/aggregator |
| business_profile | object | Business details (legal_name, trading_name, registration_number, tax_id, incorporation_date, business_type, industry, address, city, postcode, country, website) |
| ubos | array | Array of Ultimate Beneficial Owners with ownership details |
| created_at | string | Timestamp when customer was created in ISO 8601 format |
| updated_at | string | Timestamp when customer was last updated in ISO 8601 format |
Deposit
| Name | Type | Description |
|---|---|---|
| id | integer | Unique deposit id. |
| blockchain | string | Blockchain id. |
| currency | string | Deposit currency id. |
| amount | double | Deposit amount. |
| fee | double | Deposit fee. |
| txid | string | Deposit transaction id. |
| confirmations | integer | Number of deposit confirmations. |
| state | string | Deposit state. |
| created_at | string | The datetime when deposit was created. |
| completed_at | string | The datetime when deposit was completed.. |
| tid | string | The shared transaction ID |
Transaction
| Name | Type | Description |
|---|---|---|
| id | integer | Unique transaction identifier |
| type | string | Transaction type (deposit, withdraw, internal_transfer, trade, referral, maker_reward) |
| reference_type | string | Related entity type (Deposit, Withdraw, Order, etc.) |
| reference_id | integer | Related entity ID |
| credit_currency | string | Currency received (if applicable) |
| credit_amount | decimal | Amount received |
| debit_currency | string | Currency sent (if applicable) |
| debit_amount | decimal | Amount sent |
| fee_currency | string | Fee currency |
| fee | decimal | Fee amount |
| blockchain_key | string | Blockchain identifier (for crypto transactions) |
| blockchain_name | string | Blockchain name |
| market_id | string | Market ID (for trades) |
| order_id | string | Order ID (for trades) |
| price | decimal | Trade price (for trades) |
| origin_volume | decimal | Original order volume (for trades) |
| payment_method_provider | string | Payment provider (for fiat transactions) |
| payment_method_name | string | Payment method name |
| payment_method_precision | integer | Decimal precision for payment method |
| payment_method_icon_url | string | Payment method icon URL |
| voucher_pin | string | Voucher PIN (if applicable) |
| change_voucher_pin | string | Change voucher PIN (if change was issued) |
| change_voucher_expiry | string | Change voucher expiry date (ISO8601) |
| change_voucher_amount | decimal | Change voucher amount |
| description | string | Transaction description |
| member_description | string | Your personal note for this transaction |
| sender | string | Sender information |
| receiver | string | Receiver information |
| state | string | Transaction state |
| address | string | Blockchain address (for crypto transactions) |
| txid | string | Transaction ID (blockchain txid or internal reference) |
| beneficiary_id | integer | Beneficiary ID (for withdrawals) |
| paid_at | string | Payment completion timestamp (ISO8601) |
| expires_at | string | Expiry timestamp (ISO8601, for vouchers) |
| created_at | string | Transaction creation timestamp (ISO8601) |
| updated_at | string | Last update timestamp (ISO8601) |
VASP
| Name | Type | Description |
|---|---|---|
| id | integer | Unique VASP identifier (use for vasp_id) |
| name | string | VASP name |
| url | string | VASP website URL |
| string | VASP contact email | |
| state | string | VASP state (active, pending, rejected, requested) |
| icon_url | string | VASP icon/logo URL |
| created_at | string | ISO8601 timestamp of VASP registration |
PaymentReference
| Name | Type | Description |
|---|---|---|
| id | integer | Payment reference ID |
| reference | string | Unique payment reference code (used by customer for deposits) |
| uid | string | Customer sub-account UID |
| currency | string | Currency code (e.g., "btc", "zar") |
| expected_amount | string | Expected deposit amount (null if not specified) |
| deposit_action | object | Automated action configuration (null if not configured) |
| state | string | Current state (see PaymentReference States) |
| expires_at | string | ISO8601 expiration timestamp |
| usage_count | integer | Number of times the reference has been used |
| last_used_at | string | ISO8601 timestamp of last use (null if never used) |
| webhook_url | string | Webhook URL (only included if configured) |
| webhook_protocol | string | Webhook protocol: "hmac", "plain_text", or "none" (only if configured) |
| webhook_secret | string | Webhook secret (only included for owner with includeSecret option) |
| deposit_id | integer | Linked deposit ID (only included if linked) |
| order_id | integer | Linked order ID (only included if linked) |
| withdraw_id | integer | Linked withdrawal ID (only included if linked) |
| internal_transfer_id | integer | Linked internal transfer ID (only included if linked) |
| error | string | Error message (only included if state is "failed") |
| created_at | string | ISO8601 creation timestamp |
| updated_at | string | ISO8601 last update timestamp |
Deposit Action Object Structure:
| Field | Type | Description |
|---|---|---|
| action | string | Action type: "none", "aggregate", "convert_and_withdraw", "aggregate_convert_and_withdraw" |
| target_currency | string | Target currency for conversion (only for convert actions) |
| beneficiary_id | integer | Beneficiary ID for withdrawals (only for withdraw actions) |
| lightning_invoice | string | Lightning invoice for lightning withdrawals (alternative to beneficiary_id) |
| max_slippage | number | Maximum slippage percentage (0.1-10, default: 2.0) |
PaymentReference States:
Payment references progress through various states based on the configured actions. Webhook notifications are sent when the reference reaches completed or failed state.
| State | Description |
|---|---|
| pending_deposit | Waiting for deposit |
| pending_internal_transfer | Processing aggregation |
| pending_buy_order | Processing conversion (buy) |
| pending_sell_order | Processing conversion (sell) |
| pending_withdraw | Processing withdrawal |
| completed | All actions completed successfully |
| failed | Processing failed (see error field) |
Account
| Name | Type | Description |
|---|---|---|
| currency | string | Currency code. |
| balance | double | Account balance. |
| locked | double | Account locked funds. |
Deposit Method
| Name | Type | Description |
|---|---|---|
| uuid | string | Unique identifier for the deposit method |
| name | string | Display name of the deposit method |
| description | string | Human-readable description of the method |
| currencyId | string | Currency identifier (e.g., "btc", "zar") |
| symbol | string | Currency symbol (e.g., "BTC", "ZAR") |
| blockchainName | string | Blockchain network name (e.g., "bitcoin", "ethereum", "fiat") |
| type | string | Method type (e.g., "bank", "crypto", "voucher") |
| currencyType | string | Currency type: "fiat" or "crypto" |
| providerId | string | Payment provider identifier (e.g., "stitch", "xago", "cape") |
| minAmount | number | Minimum deposit amount (after baseFactor conversion) |
| maxAmount | number | Maximum deposit amount (derived from remaining limits) |
| baseFactor | number | Conversion factor (divide stored amounts by this for display) |
| precision | number | Decimal precision for display |
| feeCape | number | Cape's fee amount or percentage |
| feeProvider | number | Provider's fee amount or percentage |
| position | number | Display order position |
| iconUrl | string | Icon URL for UI display |
| verified | boolean | Whether user is verified for this method |
| remainingLimits | object | Remaining transaction limits (see Remaining Limits object below) |
Withdraw Method
| Name | Type | Description |
|---|---|---|
| uuid | string | Unique identifier for the withdraw method |
| id | number | Database ID (included for legacy support) |
| name | string | Display name of the withdraw method |
| description | string | Human-readable description of the method |
| currencyId | string | Currency identifier (e.g., "btc", "zar") |
| symbol | string | Currency symbol (e.g., "BTC", "ZAR") |
| blockchainName | string | Blockchain network name (e.g., "bitcoin", "ethereum", "fiat") |
| type | string | Method type (e.g., "crypto", "bank", "wallet") |
| currencyType | string | Currency type: "fiat" or "crypto" |
| providerId | string | Payment provider identifier (e.g., "xago", "celbux", "cape") |
| processorId | string | Processor that handles withdrawals for this method |
| minAmount | number | Minimum withdrawal amount (after baseFactor conversion) |
| maxAmount | number | Maximum withdrawal amount (derived from remaining limits) |
| baseFactor | number | Conversion factor (divide stored amounts by this for display) |
| precision | number | Decimal precision for display |
| feeCape | number | Cape's withdrawal fee amount or percentage |
| feeProvider | number | Provider's withdrawal fee amount or percentage |
| position | number | Display order position |
| iconUrl | string | Icon URL for UI display |
| verified | boolean | Whether user is verified for this method |
| remainingLimits | object | Remaining transaction limits (see Remaining Limits object below) |
Remaining Limits
The remainingLimits object is included in both Deposit Method and Withdraw Method responses.
| Name | Type | Description |
|---|---|---|
| transaction_max | number | Maximum amount allowed per single transaction |
| day | number | Remaining limit for the current day |
| week | number | Remaining limit for the current week |
| month | number | Remaining limit for the current month |
| year_to_date | number | Remaining limit for the current year |
Voucher Redemption
| Name | Type | Description |
|---|---|---|
| amount | number | The amount deposited after fees. |
| origin_amount | number | The original amount of the voucher. |
| change_voucher | Change voucher | Optional if provider offers this, a new voucher for the remaining amount |
Change voucher
| Name | Type | Description |
|---|---|---|
| change_amount | number | Amount of change from the redeemed voucher, available to be spent |
| change_expiry | string | Expiry date of the new voucher. |
| change_pin | number | The new voucher_pin to be used for redemption. |
Lightning payment
| Name | Type | Description |
|---|---|---|
| bolt | String | The invoice bolt11. |
| address | String | The address of the invoice if not bolt11. |
| amount | number | Invoice amount paid in SAT. |
| description | String | Description for this invoice in the bolt11. |
| member_description | String | Private member description for this invoice provided by member. |
| state | String | Payment state. |
| created_at | String (ISO8601 formatted) | The datetime when payment was created. |
Lightning invoice
| Name | Type | Description |
|---|---|---|
| bolt | String | The invoice in bolt11. |
| amount | BigDecimal | Invoice amount to be paid in SAT. |
| conversion_percent | BigDecimal | The amount to automatically convert to FIAT. |
| amount_msat | BigDecimal | Invoice amount to be paid in Mili SAT. |
| description | String | Description for this invoice. |
| member_description | String | Private member Description for this invoice. |
| state | String | Payment state. |
| created_at | String (ISO8601 formatted) | The datetime when invoice was created. |
| expires_at | String (ISO8601 formatted) | The datetime when invoice expires. |
| paid_at | String (ISO8601 formatted) | The datetime when invoice was paid. |
Websocket orderbook snapshot
| Name | Type | Description |
|---|---|---|
| {market}.ob-snap | Object | The object key for the subscription |
| asks | [Orderbook entry] | Array of ask / sell orderbook entries. |
| bids | [Orderbook entry] | Array of bid / buy orderbook entries. |
| sequence | integer | The current sequence of the orderbook, used to ensure updates are sequential |
Websocket orderbook increment
| Name | Type | Description |
|---|---|---|
| {market}.ob-inc | Object | The object key for the subscription |
| asks | [Orderbook entry] | Array of ask / sell orderbook entries. |
| bids | [Orderbook entry] | Array of bid / buy orderbook entries. |
| sequence | integer | The current sequence of the orderbook, used to ensure updates are sequential |
{
"btczar.ob-snap": {
"asks": [
["841039.73", "0.156431"],
["845402.1", "0.004389"],
["900000.0", "0.00001"]
],
"bids": [
["835956.8", "0.047756"],
["831012.48", "0.03749"],
["829990.51", "0.019462"],
["400000.0", "0.0001"]
],
"sequence": 9712
}
}
States
| Order states | Description |
|---|---|
| pending | Initial state - order has been submitted (but is not yet placed / confirmed) |
| wait | Order has been placed and is available on the orderbook |
| done | Order has been completely filled |
| cancel | The order has been cancelled (A cancelled limit order may still have executed trades against it) |
| reject | The order submission was rejected |
| Deposit states |
|---|
| submitted |
| canceled |
| rejected |
| accepted |
| collected |
| skipped |
| Withdraw states |
|---|
| prepared |
| submitted |
| rejected |
| accepted |
| skipped |
| processing |
| succeed |
| canceled |
| failed |
| errored |
| confirming |
Errors
| Code | Reason | Description |
|---|---|---|
| 400 | Bad Request | Your request is invalid. |
| 401 | Unauthorized | Apikey / secret / HMAC signature incorrect |
| 403 | Forbidden | You are authenticated, but unauthorized to access this endpoint. |
| 404 | Not Found | Invalid endpoint. |
| 405 | Method Not Allowed | Invalid method for endpoint eg POST instead of GET. |
| 422 | Unprocessible Entity | The server understood the request, but the data provided is incorrect. |
| 429 | Too Many Requests | You're sending too many requests too quickly. Slow down! |
| 500 | Internal Server Error | We had a problem with our server. Try again later. |
| 503 | Service Unavailable | We're temporarily offline for maintenance. Please try again later. |