Understand error responses and handle them gracefully in your integration.
All API errors return a consistent JSON structure:
{
"error": "Invoice with ID inv_abc123 not found",
"code": "INV001"
}
The error field is a human-readable message and may change - don't parse it programmatically.
The code field is a stable machine-readable module code (e.g. INV001) you can match on.
| Code | Meaning | What to do |
|---|---|---|
400 |
Bad Request | Check request body and query parameters. The message field describes the specific validation failure. |
401 |
Unauthorized | API key or secret is invalid, expired, or missing. Verify your X-API-Key and X-API-Secret headers. |
403 |
Forbidden | Your API key doesn't have the required permissions for this endpoint. Check your key's permission scope. |
404 |
Not Found | The requested resource doesn't exist or belongs to a different organization. |
409 |
Conflict | The action can't be performed in the current state. For example, approving an already-settled invoice. |
412 |
Precondition Failed | The resource is not in a state that permits this operation - a business-rule or state-machine precondition is not met (e.g., settling an invoice that has not been approved). |
429 |
Rate Limited | Too many requests. Back off and retry after the time indicated in X-RateLimit-Reset. |
| Code | Meaning | What to do |
|---|---|---|
500 |
Internal Error | An unexpected error occurred. Retry with exponential backoff. If it persists, contact support. |
502 |
Bad Gateway | A downstream service is temporarily unavailable. Retry after a short delay. |
503 |
Service Unavailable | The API is temporarily down for maintenance. Check the status page. |
504 |
Gateway Timeout | Request took too long. Retry - for bulk operations, consider smaller batch sizes. |
| Error code | Status | Description |
|---|---|---|
INV001 | 404 | Invoice ID doesn't exist or isn't accessible |
INV004 | 412 | Action not allowed in current invoice status |
INV003 | 409 | Invoice number already exists for this connection |
INV002 | 400 | Generic invoice validation error (e.g., invalid amount or missing field) |
CONN003 | 404 | Connection ID doesn't exist or isn't accessible |
CONN010 | 412 | Connection is not in the expected status (e.g., suspended or archived) |
| Error code | Status | Description |
|---|---|---|
TREAS007 | 412 | Not enough funds to complete the payment |
All API-key authentication failures - invalid key, invalid secret, revoked key - return the same response, with no distinguishing code:
{
"error": "Unauthorized",
"message": "Invalid API key or secret"
}
There is no environment-mismatch mechanism: sandbox and production keys are simply different key values, not separately validated against an environment flag.
For transient errors (429, 500, 502, 503, 504),
implement exponential backoff with jitter:
// Pseudocode
maxRetries = 3
for attempt in 0..maxRetries:
response = makeRequest()
if response.status < 500 and response.status != 429:
return response
delay = min(2^attempt * 1000, 30000) // 1s, 2s, 4s... max 30s
jitter = random(0, delay * 0.1)
sleep(delay + jitter)
429) indicate a problem with the request itself.
Retrying without changing the request will always fail. Fix the request first.
Idempotency via the requestId field is enforced only for invoice create/submit
and treasury balance/withdrawal operations. If you include a requestId on one of
these and the original request succeeded, retrying returns the original response - no
duplicate side effects. Other mutations (connections, webhooks, organization settings, API
keys, notifications, storage) are not idempotent - a retry may create a duplicate.
// Safe retry pattern (invoice create)
POST /api/v1/invoices
{
"requestId": "your-unique-id-123", // same ID on retry
"connectionId": "conn_abc",
"amount": "1500.00",
...
}
This is especially important for payment operations where duplicate processing would be costly.
exp_sand_ keys) to test error handling without real dataerror field often contains the specific validation failure - log it for debugging412 Precondition Failed errors, fetch the current resource state before retrying the action