Accounts, destinations, deposits, balances, activity¶
POST /accounts¶
Use this to provision a new end-user account. Routes derives addresses across all supported chains automatically.
Header:
Idempotency-Key(required)
Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
label |
string | No | Human-readable label for the account |
external_id |
string | No | Your identifier for this end user, for correlation with your system |
Example request:
curl -X POST https://routes.srcry.xyz/v1/accounts \
-H "Authorization: Bearer $ROUTES_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-key-123" \
-d '{
"label": "alice",
"external_id": "usr_abc123"
}'
import httpx
response = httpx.post(
"https://routes.srcry.xyz/v1/accounts",
headers={
"Authorization": f"Bearer {api_key}",
"Idempotency-Key": "unique-key-123",
},
json={
"label": "alice",
"external_id": "usr_abc123"
},
)
const response = await fetch("https://routes.srcry.xyz/v1/accounts", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": "unique-key-123",
},
body: JSON.stringify({
label: "alice",
external_id: "usr_abc123"
}),
});
Example response:
{
"request_id": "req_01J...",
"account": {
"account_id": "acct_01J...",
"label": "alice",
"external_id": "usr_abc123",
"status": "active",
"created_at_ms": 1730000000000
}
}
Response fields¶
| Field | Type | Description |
|---|---|---|
request_id |
string | Request identifier |
account.account_id |
string | Account identifier |
account.label |
string | Label (nullable) |
account.external_id |
string | Your end-user identifier (nullable) |
account.status |
string | active |
account.created_at_ms |
integer | Account creation timestamp |
Address derivation is asynchronous and typically completes within a few seconds. After the account is created, account.wallet_created webhooks fire as each chain becomes available. Wait for the relevant account.wallet_created event before provisioning deposit addresses.
GET /accounts¶
Use this to list accounts in your organization.
Query parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
status |
string | No | Filter by status: active, deactivated, export_initiated, exported |
external_id |
string | No | Filter by your end-user identifier |
limit |
integer | No | Maximum accounts to return |
cursor |
string | No | Pagination cursor from previous response |
Example response:
{
"request_id": "req_01J...",
"accounts": [
{
"account_id": "acct_01J...",
"label": "alice",
"external_id": "usr_abc123",
"status": "active",
"created_at_ms": 1730000000000
}
],
"has_more": false,
"next_cursor": null
}
POST /accounts/{account_id}/deactivate¶
Use this to suspend an account. A deactivated account cannot create new RFQs, zaps, or register new deposits. In-flight trades settle normally. Open credits remain available for cancels.
Example request:
curl -X POST https://routes.srcry.xyz/v1/accounts/acct_01J.../deactivate \
-H "Authorization: Bearer $ROUTES_API_KEY"
import httpx
response = httpx.post(
"https://routes.srcry.xyz/v1/accounts/acct_01J.../deactivate",
headers={
"Authorization": f"Bearer {api_key}",
},
)
const response = await fetch("https://routes.srcry.xyz/v1/accounts/acct_01J.../deactivate", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
},
});
Example response:
{
"request_id": "req_01J...",
"account": {
"account_id": "acct_01J...",
"status": "deactivated"
}
}
Idempotent — repeat calls return the same result. Fires account.deactivated webhook.
PATCH /accounts/{account_id}¶
Use this to update account metadata. Currently supports attaching an email address to enable self-service key export and recovery.
Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
email |
string | No | Email address for the end user. Enables public key export and email-based recovery. |
curl -X PATCH https://routes.srcry.xyz/v1/accounts/acct_01J... \
-H "Authorization: Bearer $ROUTES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com"
}'
import httpx
response = httpx.patch(
"https://routes.srcry.xyz/v1/accounts/acct_01J...",
headers={
"Authorization": f"Bearer {api_key}",
},
json={
"email": "alice@example.com"
},
)
const response = await fetch("https://routes.srcry.xyz/v1/accounts/acct_01J...", {
method: "PATCH",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "alice@example.com"
}),
});
Example response:
{
"request_id": "req_01J...",
"account": {
"account_id": "acct_01J...",
"label": "alice",
"external_id": "usr_abc123",
"email": "alice@example.com",
"status": "active",
"created_at_ms": 1730000000000
}
}
Once an email is attached, the end user can initiate key export and recovery claims without integrator assistance via the public export and public recovery endpoints.
POST /accounts/{account_id}/export-keys¶
Use this to initiate private key export. This is irreversible. The account is locked — no new RFQs, zaps, or new deposit registrations. Routes sweeps and completes all pending settlements, then delivers the encrypted private keys via the account.key_exported webhook.
Routes requires a P-256 (NIST secp256r1) public key to encrypt the exported key material. Generate a key pair before calling this endpoint and pass the public key in the request body. Only the holder of the corresponding private key can decrypt the export bundle.
Header:
Idempotency-Key(required)
Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
target_public_key |
string | Yes | Hex-encoded uncompressed P-256 public key (130 hex chars, starts with 04). Export bundles are HPKE-encrypted to this key. |
Generating a key pair¶
Generate a P-256 key pair before calling export. Keep the private key secure — you will need it to decrypt the export bundle.
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import (
Encoding, PublicFormat, NoEncryption,
)
private_key = ec.generate_private_key(ec.SECP256R1())
public_key_bytes = private_key.public_key().public_bytes(
Encoding.X962, PublicFormat.UncompressedPoint
)
target_public_key = public_key_bytes.hex() # 130 hex chars, starts with "04"
const keyPair = await crypto.subtle.generateKey(
{ name: "ECDH", namedCurve: "P-256" },
true,
["deriveBits"],
);
const publicKeyRaw = await crypto.subtle.exportKey("raw", keyPair.publicKey);
const targetPublicKey = Buffer.from(publicKeyRaw).toString("hex"); // 130 hex chars, starts with "04"
// Store keyPair.privateKey securely — needed to decrypt the export bundle
Example request:
curl -X POST https://routes.srcry.xyz/v1/accounts/acct_01J.../export-keys \
-H "Authorization: Bearer $ROUTES_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-key-456" \
-d '{
"target_public_key": "04a1b2c3...130_hex_chars"
}'
import httpx
response = httpx.post(
"https://routes.srcry.xyz/v1/accounts/acct_01J.../export-keys",
headers={
"Authorization": f"Bearer {api_key}",
"Idempotency-Key": "unique-key-456",
},
json={
"target_public_key": target_public_key
},
)
const response = await fetch("https://routes.srcry.xyz/v1/accounts/acct_01J.../export-keys", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": "unique-key-456",
},
body: JSON.stringify({
target_public_key: targetPublicKey,
}),
});
Example response:
{
"request_id": "req_01J...",
"account": {
"account_id": "acct_01J...",
"status": "export_initiated"
}
}
The export is two-phase:
export_initiated— account locked, pending settlements being sweptkey_exported— all settlements resolved, encrypted key bundles delivered viaaccount.key_exportedwebhook
Once key_exported fires, the account status is exported and Routes can no longer operate on this account. The keys array in the webhook payload contains per-chain export_bundle values encrypted to the target_public_key — only the holder of the corresponding P-256 private key can decrypt them.
POST /public/initiate-export¶
Use this to begin a user-initiated key export without integrator assistance. Requires an email attached to the account via PATCH /accounts/{account_id}.
Auth:
- No bearer token
- Rate-limited per account
Public export endpoints are protected by abuse controls. Exact thresholds are not part of the public contract.
Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
account_id |
string | Yes | Account to export keys from |
curl -X POST https://routes.srcry.xyz/v1/public/initiate-export \
-H "Content-Type: application/json" \
-d '{
"account_id": "acct_01J..."
}'
200 example:
{
"request_id": "req_01J...",
"initiated": true
}
Routes sends a one-time password (OTP) to the email on file for the account. The OTP is valid for a limited time.
| Code | HTTP | Description |
|---|---|---|
email_not_configured |
422 | Account does not have an email attached |
account_not_found |
404 | Account does not exist or is not in your organization |
account_already_exported |
409 | Account is already in export_initiated or exported state |
rate_limited |
429 | Request throttled by abuse controls |
POST /public/complete-export¶
Use this to verify the OTP and initiate the export. The account is locked and the export lifecycle begins — identical to the integrator-assisted POST /accounts/{account_id}/export-keys flow.
Auth:
- No bearer token
Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
account_id |
string | Yes | Account to export |
otp_code |
string | Yes | One-time password from email |
target_public_key |
string | Yes | Hex-encoded uncompressed P-256 public key (130 hex chars, starts with 04) |
curl -X POST https://routes.srcry.xyz/v1/public/complete-export \
-H "Content-Type: application/json" \
-d '{
"account_id": "acct_01J...",
"otp_code": "483291",
"target_public_key": "04a1b2c3...130_hex_chars"
}'
200 example:
{
"request_id": "req_01J...",
"account": {
"account_id": "acct_01J...",
"status": "export_initiated"
}
}
| Code | HTTP | Description |
|---|---|---|
invalid_otp |
401 | OTP is incorrect |
otp_expired |
401 | OTP has expired — call POST /public/initiate-export again |
account_already_exported |
409 | Account is already in export_initiated or exported state |
rate_limited |
429 | Request throttled by abuse controls |
GET /public/export-status¶
Use this to poll for export completion and retrieve the encrypted key bundle. This allows the end user to retrieve their keys directly without depending on the integrator to forward the webhook payload.
Auth:
- No bearer token
- OTP code reused as session proof
Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
account_id |
string | Yes | Account being exported |
otp_code |
string | Yes | OTP code used in POST /public/complete-export |
curl "https://routes.srcry.xyz/v1/public/export-status?account_id=acct_01J...&otp_code=483291"
While export is in progress:
{
"request_id": "req_01J...",
"status": "export_initiated",
"poll_after_ms": 5000
}
When export is complete:
{
"request_id": "req_01J...",
"status": "exported",
"keys": [
{
"chain": "solana",
"address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"export_bundle": "hpke_encrypted_base64..."
},
{
"chain": "evm:1",
"address": "0x4e83362442b8d1bec281594cea3050c8eb01311c",
"export_bundle": "hpke_encrypted_base64..."
}
]
}
Each export_bundle is HPKE-encrypted to the target_public_key provided in POST /public/complete-export. Decrypt using the corresponding P-256 private key.
| Code | HTTP | Description |
|---|---|---|
invalid_otp |
401 | OTP does not match the export session |
export_not_found |
404 | No export in progress for this account |
rate_limited |
429 | Request throttled by abuse controls |
POST /accounts/{account_id}/destinations¶
Use this to register an external delivery address for an asset. Trade, zap, and cancel requests reference a pre-registered destination_id — inline addresses are not accepted on those endpoints.
Exception: POST /public/recover-funds accepts inline destination details for recovery claims.
Destinations are screened at registration and may later be suspended by recurring delta screening. A destination is considered suspended when suspended_at_ms is non-null.
Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
asset_key |
string | One of asset_key or symbol required |
Asset this destination receives |
symbol |
string | One of asset_key or symbol required |
Asset by partner symbol |
address |
string | Yes | On-chain address |
memo |
string | No | Memo field (required when destination policy requires memo) |
tag |
string | No | Tag field (required when destination policy requires tag). For XRPL, tag is protocol-optional but may be required by receiving platforms. |
label |
string | No | Human-readable label for this destination |
Example request:
curl -X POST https://routes.srcry.xyz/v1/accounts/acct_01J.../destinations \
-H "Authorization: Bearer $ROUTES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"asset_key": "native.zec",
"address": "t1...",
"label": "Cold wallet"
}'
import httpx
response = httpx.post(
"https://routes.srcry.xyz/v1/accounts/acct_01J.../destinations",
headers={
"Authorization": f"Bearer {api_key}",
},
json={
"asset_key": "native.zec",
"address": "t1...",
"label": "Cold wallet"
},
)
const response = await fetch("https://routes.srcry.xyz/v1/accounts/acct_01J.../destinations", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
asset_key: "native.zec",
address: "t1...",
label: "Cold wallet"
}),
});
{
"asset_key": "native.zec",
"address": "t1...",
"label": "Cold wallet"
}
Example response:
{
"request_id": "req_01J...",
"destination": {
"destination_id": "dest_01J...",
"asset_key": "native.zec",
"address": "t1...",
"memo": null,
"tag": null,
"label": "Cold wallet",
"suspended_at_ms": null,
"suspension_reason": null
}
}
Response fields¶
| Field | Type | Description |
|---|---|---|
request_id |
string | Request identifier |
destination.destination_id |
string | Destination identifier |
destination.asset_key |
string | Asset key |
destination.address |
string | On-chain address |
destination.memo |
string | Memo field (nullable) |
destination.tag |
string | Tag field (nullable) |
destination.label |
string | Label (nullable) |
destination.suspended_at_ms |
integer | Suspension timestamp. null means destination is currently usable. |
destination.suspension_reason |
string | Suspension reason when suspended_at_ms is set (nullable). |
GET /accounts/{account_id}/destinations¶
Use this to list registered destinations for an account.
A destination is considered suspended when suspended_at_ms is non-null. New trades, zaps, and cancels referencing suspended destinations are rejected with 403 destination_suspended.
Useful query parameters:
asset_keylimitcursor
Example response:
{
"request_id": "req_01J...",
"destinations": [
{
"destination_id": "dest_01J...",
"asset_key": "native.zec",
"address": "t1...",
"memo": null,
"tag": null,
"label": "Cold wallet",
"suspended_at_ms": null,
"suspension_reason": null
}
],
"has_more": false,
"next_cursor": null
}
DELETE /accounts/{account_id}/destinations/{destination_id}¶
Use this to remove a registered destination. In-flight trades referencing this destination are not affected.
GET /accounts/{account_id}/deposit-addresses¶
Use this to list deposit addresses provisioned for an account.
Query parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
asset_key |
string | No | Filter by asset key |
limit |
integer | No | Maximum addresses to return |
cursor |
string | No | Pagination cursor from previous response |
Example response:
{
"request_id": "req_01J...",
"deposit_addresses": [
{
"deposit_address_id": "depaddr_01J...",
"asset_key": "native.btc",
"address": "bc1q...",
"memo": null,
"tag": null
},
{
"deposit_address_id": "depaddr_01K...",
"asset_key": "native.solana",
"address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"memo": null,
"tag": null
}
],
"has_more": false,
"next_cursor": null
}
POST /accounts/{account_id}/deposit-addresses¶
Use this to provision a deposit address for an asset.
Deposit addresses are ephemeral — Routes monitors them for incoming deposits for a bounded period. For long-lived deposit flows (e.g. a permanent deposit page), re-provision periodically. Zap deposit addresses are single-use and should not be reused across zaps.
Multiple deposit addresses for different assets on the same chain (e.g. native.solana and an SPL token) will return the same on-chain address.
Header:
Idempotency-Key(required)
Request body: exactly one of asset_key or symbol.
Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
asset_key |
string | Exactly one of asset_key or symbol required |
Asset by key |
symbol |
string | Exactly one of asset_key or symbol required |
Asset by partner symbol |
Example request:
curl -X POST https://routes.srcry.xyz/v1/accounts/acct_01J.../deposit-addresses \
-H "Authorization: Bearer $ROUTES_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-key-789" \
-d '{
"asset_key": "native.zec"
}'
import httpx
response = httpx.post(
"https://routes.srcry.xyz/v1/accounts/acct_01J.../deposit-addresses",
headers={
"Authorization": f"Bearer {api_key}",
"Idempotency-Key": "unique-key-789",
},
json={
"asset_key": "native.zec"
},
)
const response = await fetch("https://routes.srcry.xyz/v1/accounts/acct_01J.../deposit-addresses", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": "unique-key-789",
},
body: JSON.stringify({
asset_key: "native.zec"
}),
});
{
"asset_key": "native.zec"
}
Example response:
{
"request_id": "req_01J...",
"deposit_address": {
"deposit_address_id": "depaddr_01J...",
"asset_key": "native.zec",
"address": "t1...",
"memo": null,
"tag": null
}
}
Response fields¶
| Field | Type | Description |
|---|---|---|
request_id |
string | Request identifier |
deposit_address.deposit_address_id |
string | Deposit address identifier |
deposit_address.asset_key |
string | Asset key |
deposit_address.address |
string | Chain deposit address |
deposit_address.memo |
string | Memo field (nullable) |
deposit_address.tag |
string | Tag field (nullable) |
Idempotency semantics:
- same key + same payload -> same
deposit_address_id - same key + different payload ->
409 idempotency_key_reuse
Deposit flow and credits¶
When funds arrive on-chain, Routes creates a credit — a spendable balance with a unique credit_id. All trading is sourced from credits.
The deposit lifecycle:
POST /deposit-addresses → deposit_address_id + address
│
│ (end user sends funds on-chain)
│
deposit.pending → transaction detected
│
├─ chain confirmation (indexer)
├─ source address screening (Routes)
├─ partner screening (if enabled) (integrator, 10 s window)
│
│ (all must resolve without a flag)
│
├─ deposit.verified → credit_id, credited_atoms
│
└─ deposit.flagged → no credit created
Chain confirmation, source address screening, and (if enabled) partner screening run in parallel. A credit is only created when all active checks have resolved without a flag. See Transaction Monitoring for the full verification flow, partner screening, and flagged deposit recovery.
deposit_address_ididentifies a deposit address provisioned for an asset. Returned byPOST /deposit-addresses. The same address may receive multiple deposits, each producing its own credit.credit_ididentifies a spendable balance. Created when a deposit is verified. Pass it toPOST /rfqsorPOST /zapsto source a trade.
A credit is either open (available to spend) or consumed (used by a trade). Each trade fully consumes its source credit. If the trade uses less than the credit's full amount, Routes creates a new credit for the remainder — similar to change in a UTXO model.
Credits are also created when a trade settles — the output asset produces a new credit_id that can be used for subsequent trades or cancels.
A single trade can produce up to two new credits:
- Settlement credit — the output asset received from the trade
- Change credit — any unused input amount returned as a new credit
source_type indicates how a credit was created: deposit, trade_settlement, or change.
Each RFQ and zap draws from exactly one credit. Credits cannot be merged — if an account has multiple open credits for the same asset, each must be used individually. To consolidate, cancel the smaller credits to an external address and re-deposit as a single amount.
GET /accounts/{account_id}/credits¶
Use this to list spendable credits for an account.
Useful query parameters:
asset_keyorsymbolstatus:open(available to spend) orconsumed(used by a trade). Default:openlimitcursor
Example response:
{
"request_id": "req_01J...",
"credits": [
{
"credit_id": "cred_01J...",
"account_id": "acct_01J...",
"asset_key": "native.btc",
"atoms": "100000000",
"status": "open",
"source_type": "deposit",
"source_id": "dep_01J...",
"created_at_ms": 1730000006500
},
{
"credit_id": "cred_01J...",
"account_id": "acct_01J...",
"asset_key": "native.btc",
"atoms": "50000000",
"status": "open",
"source_type": "change",
"source_id": "trd_01J...",
"created_at_ms": 1730000010000
},
{
"credit_id": "cred_01J...",
"account_id": "acct_01J...",
"asset_key": "spl.solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"atoms": "5000000000",
"status": "open",
"source_type": "trade_settlement",
"source_id": "trd_01J...",
"created_at_ms": 1730000010000
}
],
"has_more": false,
"next_cursor": null
}
Response fields¶
| Field | Type | Description |
|---|---|---|
request_id |
string | Request identifier |
credits[].credit_id |
string | Credit identifier |
credits[].account_id |
string | Owning account |
credits[].asset_key |
string | Asset key |
credits[].atoms |
string | Credit amount |
credits[].status |
string | open or consumed |
credits[].source_type |
string | deposit, trade_settlement, or change |
credits[].source_id |
string | Source identifier (deposit, trade, or parent trade that produced change) |
credits[].created_at_ms |
integer | Credit creation timestamp |
has_more |
boolean | More results available |
next_cursor |
string | Pagination cursor |
GET /accounts/{account_id}/balances¶
Convenience view over credits and in-flight activity. available_atoms is the sum of all open credits for an asset. pending_atoms is the sum of funds expected but not yet credited — includes unconfirmed deposits and in-flight trade/zap settlements.
Useful query parameters:
asset_keyorsymbollimitcursor
Example response:
{
"request_id": "req_01J...",
"balances": [
{
"asset_key": "native.zec",
"available_atoms": "100000",
"pending_atoms": "0"
}
],
"has_more": false,
"next_cursor": null
}
Response fields¶
| Field | Type | Description |
|---|---|---|
request_id |
string | Request identifier |
balances[].asset_key |
string | Asset key |
balances[].available_atoms |
string | Sum of open credits in atoms |
balances[].pending_atoms |
string | Expected but not yet credited: unconfirmed deposits + in-flight settlements |
has_more |
boolean | More results available |
next_cursor |
string | Pagination cursor |
GET /accounts/{account_id}/history¶
Use this to retrieve enriched trade, zap, and cancel records for an account. Unlike the activity feed, which returns webhook-style event envelopes, history returns pre-hydrated records with asset pairs, amounts, fees, and settlement details — everything needed to render a transaction history table without follow-up API calls.
Query parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
types |
string | No | Comma-separated record types: trade, zap, cancel. Default: all |
status |
string | No | Filter by terminal status: settled, failed, confirmed (cancels) |
asset_key |
string | No | Filter to records involving this asset (either side) |
since_ms |
integer | No | Lower bound by created_at_ms |
until_ms |
integer | No | Upper bound by created_at_ms |
limit |
integer | No | Maximum records to return |
cursor |
string | No | Pagination cursor from previous response |
Example response:
{
"request_id": "req_01J...",
"records": [
{
"type": "trade",
"trade_id": "trd_01J...",
"client_order_id": "ord_uniq_123",
"status": "settled",
"from_asset_key": "spl.solana:XYZMINT...",
"to_asset_key": "native.zec",
"amount_in_atoms": "1000000000",
"amount_out_atoms": "100000000",
"fees": {
"platform_bps": "5",
"integrator_bps": "10",
"total_bps": "15",
"platform_atoms": "50000",
"integrator_atoms": "100000",
"total_atoms": "150000"
},
"settlement": {
"out_tx_hash": "0xabc123...",
"out_finalized": true,
"in_tx_hash": "0xdef456...",
"in_finalized": true
},
"created_at_ms": 1730000006500,
"settled_at_ms": 1730000010000
},
{
"type": "zap",
"zap_id": "zap_01J...",
"client_zap_id": "myzap_456",
"status": "settled",
"from_asset_key": "native.btc",
"to_asset_key": "spl.solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"requested_amount_in_atoms": "100000",
"observed_amount_in_atoms": "98500",
"amount_in_atoms": "98500",
"amount_out_atoms": "5000000",
"fees": {
"platform_bps": "5",
"integrator_bps": "0",
"total_bps": "5",
"platform_atoms": "25000",
"integrator_atoms": "0",
"total_atoms": "25000"
},
"settlement": {
"out_tx_hash": "3xKm...",
"out_finalized": true,
"in_tx_hash": "0xghi789...",
"in_finalized": true
},
"created_at_ms": 1730000020000,
"settled_at_ms": 1730000025000
},
{
"type": "cancel",
"cancel_id": "cxl_01J...",
"client_cancel_id": "cxl_uniq_123",
"status": "confirmed",
"asset_key": "native.btc",
"amount_atoms": "50000000",
"destination_address": "bc1q...",
"created_at_ms": 1730000030000,
"confirmed_at_ms": 1730000035000
}
],
"has_more": true,
"next_cursor": "opaque"
}
Response fields¶
| Field | Type | Description |
|---|---|---|
request_id |
string | Request identifier |
records[].type |
string | trade, zap, or cancel |
records[].status |
string | Terminal status of the record |
records[].created_at_ms |
integer | Record creation timestamp |
Trade and zap records include:
| Field | Type | Description |
|---|---|---|
trade_id or zap_id |
string | Resource identifier |
client_order_id or client_zap_id |
string | Client-provided identifier (nullable) |
from_asset_key |
string | Source asset |
to_asset_key |
string | Destination asset |
amount_in_atoms |
string | Input amount |
amount_out_atoms |
string | Output amount (net of fees) |
requested_amount_in_atoms |
string | Requested input amount from create request (zap records only) |
observed_amount_in_atoms |
string | Observed deposited amount used for execution (deposit-funded zap records only) |
fees |
object | Fee breakdown (see Fees) |
settlement |
object | Settlement tx hashes and finality status |
settled_at_ms |
integer | Settlement timestamp (present when settled) |
failure_code |
string | Failure reason (present when failed) |
Cancel records include:
| Field | Type | Description |
|---|---|---|
cancel_id |
string | Cancel identifier |
client_cancel_id |
string | Client-provided identifier (nullable) |
asset_key |
string | Asset moved |
amount_atoms |
string | Amount moved |
destination_address |
string | Destination address |
confirmed_at_ms |
integer | Confirmation timestamp (present when confirmed) |
failure_code |
string | Failure reason (present when failed) |
Records are ordered by created_at_ms descending (newest first).
GET /accounts/{account_id}/activity¶
Use this as the unified account ledger stream. Returns the same event types delivered via webhooks, scoped to a single account. Events carry webhook-style payloads: identifiers plus type-specific context fields. For pre-hydrated trade/zap/cancel records suitable for rendering a transaction history, use history instead.
Query parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
types |
string | No | Comma-separated event type filter (e.g. trade.*, deposit.verified) |
since_ms |
integer | No | Lower bound by created_at_ms |
until_ms |
integer | No | Upper bound by created_at_ms |
asset_key |
string | No | Filter to events involving this asset |
trade_id |
string | No | Filter to events for a specific trade |
zap_id |
string | No | Filter to events for a specific zap |
cancel_id |
string | No | Filter to events for a specific cancel |
credit_id |
string | No | Filter to events for a specific credit |
limit |
integer | No | Maximum events to return |
cursor |
string | No | Pagination cursor from previous response |
Event envelope¶
Each event in the events array uses the same envelope as webhook deliveries:
{
"event_id": "evt_01J...",
"type": "deposit.verified",
"created_at_ms": 1730000006500,
"account_id": "acct_01J...",
"data": {
"deposit_address_id": "depaddr_01J...",
"credit_id": "cred_01J...",
"asset_key": "native.btc",
"credited_atoms": "100000",
"source_address": "bc1q..."
}
}
See Webhooks — Event types for the full list of type values and their data payloads.
Example response:
{
"request_id": "req_01J...",
"events": [
{
"event_id": "evt_01J...",
"type": "deposit.verified",
"created_at_ms": 1730000006500,
"account_id": "acct_01J...",
"data": {
"deposit_address_id": "depaddr_01J...",
"credit_id": "cred_01J...",
"asset_key": "native.btc",
"credited_atoms": "100000",
"source_address": "bc1q..."
}
},
{
"event_id": "evt_01J...",
"type": "trade.settled",
"created_at_ms": 1730000010000,
"account_id": "acct_01J...",
"data": {
"trade_id": "trd_01J...",
"quote_id": "qt_01J...",
"client_order_id": "ord_uniq_123",
"credit_id": "cred_01J..."
}
}
],
"has_more": true,
"next_cursor": "opaque"
}
Response fields¶
| Field | Type | Description |
|---|---|---|
request_id |
string | Request identifier |
events[].event_id |
string | Globally unique event identifier (dedupe key) |
events[].type |
string | Event type (e.g. deposit.verified, trade.settled) |
events[].created_at_ms |
integer | Event creation timestamp |
events[].data |
object | Type-specific payload |
has_more |
boolean | More results available |
next_cursor |
string | Pagination cursor |