All errors return a JSON body with a stable error code, a human-readable message, and optional details for validation issues. Use error (not message) for programmatic handling.

Success

StatusUse
200OK — body per route
201Created — Location header may be set

Client errors

StatusWhenWhat to do
400Validation failureRead details for field-level errors; fix and retry
401Missing or invalid API keyCheck the Authorization header; rotate the key if compromised
402Insufficient free balanceTop up; read required_amount and topup_url
403Policy violation, agent not approved, or test phone number mismatchFix the agent state or the target number; do not retry blindly
404Unknown resource or device not RCS-capableVerify ids and environment; for RCS, the device may not support RCS
409Conflict — duplicate or wrong stateRe-read the resource; avoid blind retries
422Semantic validationFix domain-specific rules described in the body
429Rate limitHonor retry_after; back off with jitter

Server errors

StatusWhen
500Unexpected internal error
503Dependency temporarily unavailable
Retry 500 / 503 only with an idempotency key on mutating routes and exponential backoff. Do not retry non-idempotent requests without a key.

Error envelope

{
  "error": "insufficient_balance",
  "message": "Free balance is below the required hold amount.",
  "details": []
}
error
string
Machine-readable error code. Stable across versions — use this for conditionals.
message
string
Human-readable explanation. Safe to log; do not use for programmatic branching.
details
array
Present on validation errors (400, 422). Each item includes field and message.

402 — Insufficient balance

The most actionable error: the free balance is below the hold amount needed for the operation.
{
  "error": "insufficient_balance",
  "message": "Free balance is below the required hold amount.",
  "balance": {
    "free": 0.05,
    "reserved": 10.00,
    "total": 10.05
  },
  "required_amount": 0.12,
  "topup_url": "https://app.arowana.app/billing"
}
balance.free
number
Immediately spendable balance. This is what the check runs against.
balance.reserved
number
Currently held for in-flight operations.
required_amount
number
Exact shortfall — add at least this amount to complete the operation.
topup_url
string
Direct link to the billing top-up flow in the dashboard.

403 — Test Agent phone number

Test Agent sends return a generic 403 when the target number is not the verified phone number. The response does not reveal whether the number belongs to another account.
  • Conventions — idempotency, rate limits, and retry patterns
  • Billing API — proactive balance checks before large sends
  • Our philosophy — why errors are designed to be actionable