Skip to content

Create an API key

POST
/api-keys
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 to fall back to the legacy coarse permission. Available scopes: wallets:read|write, customers:read|write, transactions:read, withdrawals:read|write, webhooks:read|write, fees:read, api_keys:read|write.

Media typeapplication/json
object
label
required
string
<= 100 characters
scopes

Fine-grained scopes (preferred over permission). Must be a subset of the calling key’s own scopes. Omit to use permission.

Array<string>
Allowed values: wallets:read wallets:write customers:read customers:write transactions:read withdrawals:read withdrawals:write webhooks:read webhooks:write fees:read api_keys:read api_keys:write
permission

Legacy. Used only when scopes is omitted.

string
default: READ_WRITE
Allowed values: READ_ONLY READ_WRITE
allowed_ips

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.

Array<string>
<= 50 items
Example
[
"203.0.113.4",
"198.51.100.0/24"
]
expires_at

ISO 8601 expiry datetime. Omit for a non-expiring key.

string format: date-time

Key created. secret shown only at creation, store it securely.

Media typeapplication/json
object
id
string format: uuid
label
string
key_id
string
scopes

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).

Array<string>
Allowed values: wallets:read wallets:write customers:read customers:write transactions:read withdrawals:read withdrawals:write webhooks:read webhooks:write fees:read api_keys:read api_keys:write
permission

Legacy coarse permission. Retained for display and as the fallback when scopes is empty.

string
Allowed values: READ_ONLY READ_WRITE
allowed_ips

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).

Array<string>
is_test
boolean
last_used_at
Any of:
string format: date-time
expires_at
Any of:
string format: date-time
created_at
string format: date-time
secret

HMAC signing secret, shown once only.

string
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)

Media typeapplication/json
object
error
required

Human-readable message, for display/logging. Do NOT branch on this text.

string
code
required

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, 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. 326 throw sites raise a generic one (VALIDATION_ERROR, NOT_FOUND, UNPROCESSABLE, UNAUTHORIZED, CONFLICT, FORBIDDEN) against 46 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.

string
Example
{
"error": "Insufficient balance. Available: 10.000000, Requested: 50.000000",
"code": "INSUFFICIENT_BALANCE"
}

Invalid or revoked API key / HMAC signature mismatch

Media typeapplication/json
object
error
required

Human-readable message, for display/logging. Do NOT branch on this text.

string
code
required

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, 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. 326 throw sites raise a generic one (VALIDATION_ERROR, NOT_FOUND, UNPROCESSABLE, UNAUTHORIZED, CONFLICT, FORBIDDEN) against 46 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.

string
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

Media typeapplication/json
object
error
required

Human-readable message, for display/logging. Do NOT branch on this text.

string
code
required

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, 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. 326 throw sites raise a generic one (VALIDATION_ERROR, NOT_FOUND, UNPROCESSABLE, UNAUTHORIZED, CONFLICT, FORBIDDEN) against 46 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.

string
Example
{
"error": "Insufficient balance. Available: 10.000000, Requested: 50.000000",
"code": "INSUFFICIENT_BALANCE"
}

Rate limit exceeded

Media typeapplication/json
object
error
required

Human-readable message, for display/logging. Do NOT branch on this text.

string
code
required

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, 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. 326 throw sites raise a generic one (VALIDATION_ERROR, NOT_FOUND, UNPROCESSABLE, UNAUTHORIZED, CONFLICT, FORBIDDEN) against 46 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.

string
Example
{
"error": "Insufficient balance. Available: 10.000000, Requested: 50.000000",
"code": "INSUFFICIENT_BALANCE"
}