Create an API key
const url = 'https://example.com/v2/api-keys';const options = { method: 'POST', headers: {'X-CowriePay-Key': '<X-CowriePay-Key>', 'Content-Type': 'application/json'}, body: '{"label":"example","scopes":["wallets:read"],"permission":"READ_ONLY","allowed_ips":["203.0.113.4","198.51.100.0/24"],"expires_at":"2026-04-15T12:00:00Z"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/v2/api-keys \ --header 'Content-Type: application/json' \ --header 'X-CowriePay-Key: <X-CowriePay-Key>' \ --data '{ "label": "example", "scopes": [ "wallets:read" ], "permission": "READ_ONLY", "allowed_ips": [ "203.0.113.4", "198.51.100.0/24" ], "expires_at": "2026-04-15T12:00:00Z" }'The secret field is returned only at creation time and cannot be retrieved again.
Store it securely, it is used as the HMAC signing secret for all subsequent requests.
Requires the api_keys:write scope.
Scopes (recommended): pass scopes to mint a least-privilege key. A key may only
grant scopes it itself holds (no privilege escalation). Omit scopes and the coarse
permission is converted into the explicit scopes it stands for (READ_WRITE: every scope
your workspace can grant; READ_ONLY: every read scope); the calling key must hold every one
of them, or the request is refused with 403. The new key always carries explicit scopes.
Available scopes: wallets:read|write,
customers:read|write, transactions:read,
withdrawals:read|write, webhooks:read|write, fees:read, api_keys:read|write.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
Normalised (Unicode NFKC, runs of whitespace collapsed); a text-direction control or an invisible character is refused with 400.
Fine-grained scopes (preferred over permission). Must be a subset of the calling key’s own scopes. Omit to use permission, which is converted into explicit scopes under the same rule.
Coarse alternative, used only when scopes is omitted, and converted into explicit scopes.
Optional source-IP allowlist. When set, requests from a source IP outside this list are rejected with 403 IP_NOT_ALLOWED. Each entry is a single IP or CIDR range (IPv4 or IPv6). Omit or pass an empty array for no restriction.
Example
[ "203.0.113.4", "198.51.100.0/24"]ISO 8601 expiry datetime. Omit for a non-expiring key.
Responses
Section titled “Responses”Key created. secret shown only at creation, store it securely.
object
Fine-grained scopes granted to this key. Empty means a legacy key whose access is derived from permission (READ_WRITE → all scopes, READ_ONLY → all :read scopes).
Legacy coarse permission. Retained for display and as the fallback when scopes is empty.
Optional source-IP allowlist. When non-empty, a request from a source IP outside this list is rejected with 403 IP_NOT_ALLOWED (checked before the HMAC signature). Empty means no restriction. Each entry is a single IP or CIDR range (IPv4 or IPv6).
HMAC signing secret, shown once only.
Example
{ "key_id": "cpk_live_xxxxxxxxxxxxxxxxxxxx", "scopes": [ "wallets:read" ], "permission": "READ_ONLY", "allowed_ips": [ "203.0.113.4", "198.51.100.0/24" ]}Validation error (e.g. an unknown scope)
object
Human-readable message, for display/logging. Do NOT branch on this text.
Stable, machine-readable error code, branch on this, not the message. Common values: VALIDATION_ERROR, INVALID_JSON, PAYLOAD_TOO_LARGE, MISSING_AUTH_HEADERS, INVALID_SIGNATURE, TIMESTAMP_EXPIRED, REPLAY_DETECTED, INVALID_API_KEY, IP_NOT_ALLOWED, API_KEY_EXPIRED, WORKSPACE_INACTIVE, INSUFFICIENT_SCOPE, NOT_FOUND, CONFLICT, UNPROCESSABLE, INSUFFICIENT_BALANCE, INVALID_DESTINATION_ADDRESS, DESTINATION_IS_INTERNAL, CHAIN_NOT_ENABLED, NATIVE_WITHDRAWAL_NOT_AVAILABLE, WITHDRAWAL_RATE_LIMITED, FAUCET_ONLY_SANDBOX, FAUCET_ADDRESS_NOT_FOUND, FAUCET_INVALID_ASSET, FAUCET_AMOUNT_OVER_CAP, FAUCET_RATE_LIMITED, FAUCET_TOO_MANY_ITEMS, FAUCET_DUPLICATE_ASSET, FAUCET_NATIVE_REQUIRES_DEPOSIT, FAUCET_NATIVE_BUDGET_EXCEEDED, FAUCET_DISPENSER_EXHAUSTED, WITHDRAWAL_NOT_CANCELLABLE, IDEMPOTENCY_CONFLICT, IDEMPOTENCY_MISMATCH, IDEMPOTENCY_KEY_INVALID, FEATURE_NOT_AVAILABLE, RATE_LIMITED, DATABASE_ERROR, INTERNAL_ERROR, UNAUTHORIZED, FORBIDDEN, UNSUPPORTED_MEDIA_TYPE, WITHDRAWALS_FROZEN, WORKSPACE_NOT_FOUND, SYSTEM_WORKSPACE_PROTECTED, FAUCET_INVALID_AMOUNT, FAUCET_NO_ASSETS, FAUCET_NOT_SANDBOX.
Most failures do NOT carry a business code. 373 throw sites raise a generic one (VALIDATION_ERROR, NOT_FOUND, UNPROCESSABLE, UNAUTHORIZED, CONFLICT, FORBIDDEN) against 48 named public codes, so a generic code is the common case and a named one is the exception. Branch on code; treat error as human text that may be reworded. The full catalogue, with causes and next steps, is the Error codes guide.
Example
{ "error": "Insufficient balance. Available: 10.000000, Requested: 50.000000", "code": "INSUFFICIENT_BALANCE"}Invalid or revoked API key / HMAC signature mismatch
object
Human-readable message, for display/logging. Do NOT branch on this text.
Stable, machine-readable error code, branch on this, not the message. Common values: VALIDATION_ERROR, INVALID_JSON, PAYLOAD_TOO_LARGE, MISSING_AUTH_HEADERS, INVALID_SIGNATURE, TIMESTAMP_EXPIRED, REPLAY_DETECTED, INVALID_API_KEY, IP_NOT_ALLOWED, API_KEY_EXPIRED, WORKSPACE_INACTIVE, INSUFFICIENT_SCOPE, NOT_FOUND, CONFLICT, UNPROCESSABLE, INSUFFICIENT_BALANCE, INVALID_DESTINATION_ADDRESS, DESTINATION_IS_INTERNAL, CHAIN_NOT_ENABLED, NATIVE_WITHDRAWAL_NOT_AVAILABLE, WITHDRAWAL_RATE_LIMITED, FAUCET_ONLY_SANDBOX, FAUCET_ADDRESS_NOT_FOUND, FAUCET_INVALID_ASSET, FAUCET_AMOUNT_OVER_CAP, FAUCET_RATE_LIMITED, FAUCET_TOO_MANY_ITEMS, FAUCET_DUPLICATE_ASSET, FAUCET_NATIVE_REQUIRES_DEPOSIT, FAUCET_NATIVE_BUDGET_EXCEEDED, FAUCET_DISPENSER_EXHAUSTED, WITHDRAWAL_NOT_CANCELLABLE, IDEMPOTENCY_CONFLICT, IDEMPOTENCY_MISMATCH, IDEMPOTENCY_KEY_INVALID, FEATURE_NOT_AVAILABLE, RATE_LIMITED, DATABASE_ERROR, INTERNAL_ERROR, UNAUTHORIZED, FORBIDDEN, UNSUPPORTED_MEDIA_TYPE, WITHDRAWALS_FROZEN, WORKSPACE_NOT_FOUND, SYSTEM_WORKSPACE_PROTECTED, FAUCET_INVALID_AMOUNT, FAUCET_NO_ASSETS, FAUCET_NOT_SANDBOX.
Most failures do NOT carry a business code. 373 throw sites raise a generic one (VALIDATION_ERROR, NOT_FOUND, UNPROCESSABLE, UNAUTHORIZED, CONFLICT, FORBIDDEN) against 48 named public codes, so a generic code is the common case and a named one is the exception. Branch on code; treat error as human text that may be reworded. The full catalogue, with causes and next steps, is the Error codes guide.
Example
{ "error": "Insufficient balance. Available: 10.000000, Requested: 50.000000", "code": "INSUFFICIENT_BALANCE"}Missing the api_keys:write scope, or attempting to grant scopes the calling key does not hold
object
Human-readable message, for display/logging. Do NOT branch on this text.
Stable, machine-readable error code, branch on this, not the message. Common values: VALIDATION_ERROR, INVALID_JSON, PAYLOAD_TOO_LARGE, MISSING_AUTH_HEADERS, INVALID_SIGNATURE, TIMESTAMP_EXPIRED, REPLAY_DETECTED, INVALID_API_KEY, IP_NOT_ALLOWED, API_KEY_EXPIRED, WORKSPACE_INACTIVE, INSUFFICIENT_SCOPE, NOT_FOUND, CONFLICT, UNPROCESSABLE, INSUFFICIENT_BALANCE, INVALID_DESTINATION_ADDRESS, DESTINATION_IS_INTERNAL, CHAIN_NOT_ENABLED, NATIVE_WITHDRAWAL_NOT_AVAILABLE, WITHDRAWAL_RATE_LIMITED, FAUCET_ONLY_SANDBOX, FAUCET_ADDRESS_NOT_FOUND, FAUCET_INVALID_ASSET, FAUCET_AMOUNT_OVER_CAP, FAUCET_RATE_LIMITED, FAUCET_TOO_MANY_ITEMS, FAUCET_DUPLICATE_ASSET, FAUCET_NATIVE_REQUIRES_DEPOSIT, FAUCET_NATIVE_BUDGET_EXCEEDED, FAUCET_DISPENSER_EXHAUSTED, WITHDRAWAL_NOT_CANCELLABLE, IDEMPOTENCY_CONFLICT, IDEMPOTENCY_MISMATCH, IDEMPOTENCY_KEY_INVALID, FEATURE_NOT_AVAILABLE, RATE_LIMITED, DATABASE_ERROR, INTERNAL_ERROR, UNAUTHORIZED, FORBIDDEN, UNSUPPORTED_MEDIA_TYPE, WITHDRAWALS_FROZEN, WORKSPACE_NOT_FOUND, SYSTEM_WORKSPACE_PROTECTED, FAUCET_INVALID_AMOUNT, FAUCET_NO_ASSETS, FAUCET_NOT_SANDBOX.
Most failures do NOT carry a business code. 373 throw sites raise a generic one (VALIDATION_ERROR, NOT_FOUND, UNPROCESSABLE, UNAUTHORIZED, CONFLICT, FORBIDDEN) against 48 named public codes, so a generic code is the common case and a named one is the exception. Branch on code; treat error as human text that may be reworded. The full catalogue, with causes and next steps, is the Error codes guide.
Example
{ "error": "Insufficient balance. Available: 10.000000, Requested: 50.000000", "code": "INSUFFICIENT_BALANCE"}Rate limit exceeded
object
Human-readable message, for display/logging. Do NOT branch on this text.
Stable, machine-readable error code, branch on this, not the message. Common values: VALIDATION_ERROR, INVALID_JSON, PAYLOAD_TOO_LARGE, MISSING_AUTH_HEADERS, INVALID_SIGNATURE, TIMESTAMP_EXPIRED, REPLAY_DETECTED, INVALID_API_KEY, IP_NOT_ALLOWED, API_KEY_EXPIRED, WORKSPACE_INACTIVE, INSUFFICIENT_SCOPE, NOT_FOUND, CONFLICT, UNPROCESSABLE, INSUFFICIENT_BALANCE, INVALID_DESTINATION_ADDRESS, DESTINATION_IS_INTERNAL, CHAIN_NOT_ENABLED, NATIVE_WITHDRAWAL_NOT_AVAILABLE, WITHDRAWAL_RATE_LIMITED, FAUCET_ONLY_SANDBOX, FAUCET_ADDRESS_NOT_FOUND, FAUCET_INVALID_ASSET, FAUCET_AMOUNT_OVER_CAP, FAUCET_RATE_LIMITED, FAUCET_TOO_MANY_ITEMS, FAUCET_DUPLICATE_ASSET, FAUCET_NATIVE_REQUIRES_DEPOSIT, FAUCET_NATIVE_BUDGET_EXCEEDED, FAUCET_DISPENSER_EXHAUSTED, WITHDRAWAL_NOT_CANCELLABLE, IDEMPOTENCY_CONFLICT, IDEMPOTENCY_MISMATCH, IDEMPOTENCY_KEY_INVALID, FEATURE_NOT_AVAILABLE, RATE_LIMITED, DATABASE_ERROR, INTERNAL_ERROR, UNAUTHORIZED, FORBIDDEN, UNSUPPORTED_MEDIA_TYPE, WITHDRAWALS_FROZEN, WORKSPACE_NOT_FOUND, SYSTEM_WORKSPACE_PROTECTED, FAUCET_INVALID_AMOUNT, FAUCET_NO_ASSETS, FAUCET_NOT_SANDBOX.
Most failures do NOT carry a business code. 373 throw sites raise a generic one (VALIDATION_ERROR, NOT_FOUND, UNPROCESSABLE, UNAUTHORIZED, CONFLICT, FORBIDDEN) against 48 named public codes, so a generic code is the common case and a named one is the exception. Branch on code; treat error as human text that may be reworded. The full catalogue, with causes and next steps, is the Error codes guide.
Example
{ "error": "Insufficient balance. Available: 10.000000, Requested: 50.000000", "code": "INSUFFICIENT_BALANCE"}