Skip to content

Errors

Errors are typed and predictable. Branch on error.code (a stable string), not on the human-readable message.

{
"success": false,
"error": {
"code": "insufficient_funds",
"message": "Wallet balance is below the first cycle amount.",
"details": { }
},
"meta": { "requestId": "req_aBcDeFgHiJkL" }
}
  • error.code — stable, machine-readable. The thing to switch on.
  • error.message — human-readable; may change. Don’t parse it.
  • error.details — present for validation errors (field-level info).
  • meta.requestId — include this when contacting support; it ties to your request in the logs.
StatusMeaning
400Bad request — missing idempotency key, business-rule violation (e.g. insufficient_funds).
401Authentication failed — missing/invalid/expired/revoked key.
403Authenticated but not allowed (e.g. onboarding_incomplete).
404Resource not found.
409Conflict — duplicate, or a request already in progress.
422Validation failed, or idempotency-key reused with a different body.
429Rate limited.
500Internal error — safe to retry with the same idempotency key.
CodeHTTPWhen
idempotency_required400Idempotency-Key header missing on a write.
idempotency_error422Same idempotency key reused with a different body.
request_in_progress409A request with that idempotency key is still running.
validation_error400Request body/query failed validation.
authentication_error401Auth failed.
permission_error403Authenticated but not authorized.
not_found404Generic not found.
rate_limit_error429Too many requests.
internal_error500Server error.
CodeHTTPWhen
insufficient_funds400Wallet can’t cover the charge.
wallet_cap_exceeded400Top-up would exceed the KYC-tier cap.
CodeHTTPWhen
business_not_found404No business for this actor.
business_already_exists409One business per user (Phase 1).
onboarding_incomplete403Finish onboarding/KYC before this call.
api_key_not_found404Unknown API key.
api_key_expired401API key past expiry.
api_key_revoked401API key revoked.
CodeHTTPWhen
product_not_found404Unknown product.
product_inactive400Can’t add a price to an archived product.
price_not_found404Unknown price.
price_not_active400Can’t subscribe to an inactive price.
CodeHTTPWhen
subscription_not_found404Unknown subscription.
subscription_inactive400Subscription not in an active state.
subscription_cannot_cancel400Can’t cancel from the current state.
subscription_already_exists409Customer already subscribed to this price.
trial_not_available400No trial available for this subscription.
invoice_not_found404Unknown invoice.
invoice_not_editable400Invoice not in an editable state.
CodeHTTPWhen
customer_not_found404Unknown customer.
customer_already_exists409Email already used for this business.
event_not_found404Unknown event.
webhook_endpoint_not_found404Unknown webhook endpoint.
webhook_endpoint_disabled400Endpoint is disabled.
webhook_signature_invalid401Signature verification failed.
webhook_no_endpoints400No endpoints registered for the event.
team_invite_not_found404Unknown team invite.
team_member_conflict409Already a member or invited.
const res = await fetch(/* ... */);
const body = await res.json();
if (!body.success) {
switch (body.error.code) {
case "insufficient_funds":
return promptTopUp();
case "rate_limit_error":
return backoffAndRetry();
case "validation_error":
return showFieldErrors(body.error.details);
default:
logWithRequestId(body.meta.requestId, body.error);
}
}