Skip to main content
The Qustody API is a JSON over HTTPS API. Every endpoint accepts and returns application/json and is versioned under /v1. The full machine-readable spec is available at /api-reference/openapi.json and is rendered at API reference.

Base URL

https://api.your-qustody-host.example
Set CUSTODY_ENV=production to enforce HTTPS for tenant URLs and webhook endpoints. Development environments may use HTTP.

Resource families

FamilyPath prefixWhat it manages
Tenants/v1/tenantsTenant accounts (multi-tenant deployments)
Vaults/v1/vaultsLogical groupings of wallets
Wallets/v1/walletsQuantum Chain addresses with registered post-quantum public keys
Transactions/v1/transactionsOutbound transfers, lifecycle, signing payloads
Approvals/v1/transactions/{id}/approvalsMulti-approver workflow
Policies/v1/policiesSpending limits, whitelists, approval rules
Webhooks/v1/webhooksOutbound event delivery
Assets/v1/assetsToken metadata for QRC-20 and native assets
Users/v1/usersHuman users (when SSO is enabled)
Roles/v1/rolesRBAC role definitions and assignments
Credentials/v1/credentialsAPI keys, rotation, revocation
Audit/v1/auditTamper-evident audit log
Compliance/v1/complianceAML screenings and travel-rule data

Authentication

All non-public endpoints require authentication. Two modes are supported.

API key

curl "$BASE_URL/v1/wallets" \
  -H "Authorization: Bearer $API_KEY"
API keys are created with POST /v1/credentials and scoped to a tenant. Rotate with POST /v1/credentials/rotate.

SSO (OIDC)

Enabled with CUSTODY_SSO_ENABLED=true. Exchange your IdP token for a Qustody access token at POST /v1/auth/login, then send it in the Authorization: Bearer header. Refresh with POST /v1/auth/refresh.

Idempotency

Every mutating endpoint accepts an Idempotency-Key header. Duplicate requests with the same key within 24 hours return the original response without re-executing.
curl -X POST "$BASE_URL/v1/transactions" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: 6a8b8b3e-77d0-4f60-9a18-6f4d3e3e1f3a" \
  -H "Content-Type: application/json" \
  -d '{ "wallet_id": "...", "to_address": "0x...", "value": "1000000000000000000" }'
Use a UUIDv4 per logical operation. Reusing keys across different requests is an error.

Pagination

List endpoints use cursor-based pagination:
curl "$BASE_URL/v1/transactions?limit=50&cursor=eyJpZCI6Li4ufQ==" \
  -H "Authorization: Bearer $API_KEY"
{
  "items": [...],
  "next_cursor": "eyJpZCI6Li4ufQ==",
  "has_more": true
}
When has_more is false, next_cursor is null. limit defaults to 50 and may not exceed 200.

Filtering and sorting

Common query parameters:
ParamTypeNotes
statusstringFilter transactions by state (e.g. PENDING_SIGNATURE)
wallet_iduuidScope to one wallet
vault_iduuidScope to one vault
created_after, created_beforeRFC3339Time range
sortstringcreated_at:desc (default) or created_at:asc

Rate limits

Default rate limit is 120 requests per minute per API key with a burst of 20. Configurable via CUSTODY_RATE_LIMIT_RPM and CUSTODY_RATE_LIMIT_BURST. When exceeded, the API returns 429 Too Many Requests with Retry-After header.
HTTP/1.1 429 Too Many Requests
Retry-After: 12
For multi-replica deployments, configure a Redis backend with CUSTODY_RATE_LIMIT_REDIS_URL so limits are enforced cluster-wide.

Standard response shape

Successful single-resource responses return the resource directly:
{
  "id": "...",
  "status": "SUBMITTED",
  "created_at": "2024-..."
}
List responses are wrapped:
{
  "items": [ /* ... */ ],
  "next_cursor": "...",
  "has_more": true
}

Error envelope

All errors return:
{
  "error": {
    "code": 1404,
    "type": "not_found",
    "message": "Wallet not found",
    "request_id": "req_01HXYZ..."
  }
}
The full code catalog is at Errors and codes.

Standard headers

HeaderDirectionPurpose
Authorization: Bearer <token>RequestAuthentication
Idempotency-Key: <uuid>Request (mutating)Safe retries
X-Request-IDResponseTrace ID; quote it in support requests
Retry-AfterResponse (429, 503)Seconds to wait before retrying
X-Webhook-ID, X-Webhook-Timestamp, X-Webhook-SignatureWebhook deliveryHMAC-SHA256 verification

Versioning

The API is versioned in the URL path (/v1). Breaking changes ship as /v2 with a deprecation period announced in the changelog. Additive changes (new fields, new endpoints) are released within /v1.

Health

EndpointAuth requiredPurpose
GET /healthzNoLiveness
GET /readyzNoReadiness (DB + signer)
GET /metricsNo (port 9090)Prometheus metrics