Skip to content

Create a customer (sub-account)

POST
/customers
curl --request POST \
--url https://example.com/v2/customers \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890' \
--header 'X-CowriePay-Key: <X-CowriePay-Key>' \
--data '{ "external_ref": "example", "label": "example", "metadata": {} }'

Creates a sub-account for one of your end-users. external_ref is YOUR opaque id and is unique within your workspace + network, a duplicate returns 409. Supports Idempotency-Key. Requires customers:write.

Idempotency-Key
string
Example
a1b2c3d4-e5f6-7890-abcd-ef1234567890

Optional client-chosen key (1-255 chars, A-Z a-z 0-9 _ -; a UUIDv4 is recommended) to make this POST safe to retry. The first request executes; any later request with the SAME key returns the original response unchanged (with header Idempotent-Replay: true) WITHOUT re-running the operation, so a retried request never double-creates. Reusing a key with a different request body returns 422 (code: IDEMPOTENCY_MISMATCH); a key whose first request is still in flight returns 409 (code: IDEMPOTENCY_CONFLICT). Stored for 24h.

Media typeapplication/json
object
external_ref
required

Your opaque id for this end-user (never PII).

string
<= 255 characters
label

Optional display name.

string
<= 255 characters
metadata
object
key
additional properties
any
Examplegenerated
{
"external_ref": "example",
"label": "example",
"metadata": {}
}

Customer created

Media typeapplication/json
object
id
string format: uuid
external_ref

Your opaque identifier for this end-user (never PII). Unique within your workspace + mode.

string
label

Optional display name you chose (e.g. “Alice”). Not for PII.

string | null
metadata

Optional free-form object, opaque to CowriePay.

object | null
status

ARCHIVED retires the sub-account label; it does NOT stop monitoring its addresses.

string
Allowed values: ACTIVE ARCHIVED
created_at
string format: date-time
updated_at
string format: date-time
Example
{
"external_ref": "user_8f3a2c",
"status": "ACTIVE"
}

Validation error

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 API key / signature

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 customers:write 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"
}

A customer with this external_ref already exists in this mode

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"
}