Error Handling
BillerAPI uses conventional HTTP status codes and structured error responses to indicate the success or failure of API requests.
Error Response Format
All error responses return a JSON body with a consistent structure:
Error response
{
"status_code": 400,
"error": "validation_error",
"message": "The 'biller_id' field is required.",
"documentation_url": "https://docs.billerapi.com/concepts/errors"
}status_codeThe HTTP status code of the response.errorA machine-readable error code (see table below).messageA human-readable description of the problem.documentation_urlA link to relevant documentation for this error type.HTTP Status Codes
| Code | Meaning | Description |
|---|---|---|
| 200 | OK | Request succeeded. |
| 201 | Created | Resource was created successfully. |
| 202 | Accepted | Request accepted for async processing (e.g., bill sync). |
| 400 | Bad Request | Invalid request body or parameters. |
| 401 | Unauthorized | Missing or invalid authentication credentials. |
| 403 | Forbidden | Valid credentials but insufficient permissions. |
| 404 | Not Found | The requested resource does not exist. |
| 429 | Too Many Requests | Rate limit exceeded. See Retry-After header. |
| 500 | Internal Server Error | Something went wrong on our end. Contact support if persistent. |
Error Codes
The error field contains one of these machine-readable codes:
| Error Code | HTTP Status | Description |
|---|---|---|
| validation_error | 400 | Request body or query params failed validation. |
| authentication_error | 401 | Invalid or missing API credentials. |
| authorization_error | 403 | Authenticated but not authorized for this resource. |
| not_found | 404 | The resource does not exist or is not accessible. |
| rate_limit_exceeded | 429 | Too many requests. Retry after the indicated period. |
| internal_error | 500 | An unexpected error occurred on the server. |
Handling Errors
Always check the HTTP status code and parse the error response body for details.
Error handling
try {
const response = await fetch('https://sandbox.api.billerapi.com/billers', {
headers: {
'X-Client-ID': process.env.BILLERAPI_CLIENT_ID,
'X-Client-Secret': process.env.BILLERAPI_CLIENT_SECRET,
},
});
if (!response.ok) {
const error = await response.json();
console.error(`[${error.status_code}] ${error.error}: ${error.message}`);
if (error.error === 'rate_limit_exceeded') {
const retryAfter = response.headers.get('Retry-After');
console.log(`Retry after ${retryAfter} seconds`);
}
return;
}
const data = await response.json();
console.log(data);
} catch (err) {
console.error('Network error:', err.message);
}404 after a webhook is normal
BillerAPI’s webhook bodies are intentionally minimal — they carry the envelope plus a small data.object with the resource ID and a few routing keys. To get the full resource you call GET /v1/<resource>/<id> after handling the webhook.
That follow-up GET can return 404 Not Found even though you just received an event for the resource. This is a normal consequence of two facts:
- Webhook delivery is asynchronous; minutes can pass between the event being emitted and your handler running.
- Some resources are short-lived (a link can be disconnected, a request-to-link can be cancelled, a bill can be re-extracted with a new id).
Treat 404 as “gone is gone”: log the event id and return 200 OK from your webhook endpoint. Do not return non-2xx — that triggers BillerAPI to retry the same webhook, which will hit the same 404 on the next attempt and burn your retry budget. The state the event reflected (e.g. link.disconnected) is still actionable from the envelope alone.
Tolerating 404 on the follow-up GET
async function handleBillCreated(envelope) {
const billId = envelope.data.object.id;
const res = await fetch(`https://api.billerapi.com/v1/bills/${billId}`, {
headers: { Authorization: `Bearer ${linkScopedToken}` },
});
if (res.status === 404) {
// Resource gone between webhook fan-out and our follow-up GET.
// Acknowledge so we are not retried; we already know the bill id.
logger.info('bill_gone_at_consume_time', { event_id: envelope.id, bill_id: billId });
return; // caller returns 200 OK
}
if (!res.ok) throw new Error(`unexpected ${res.status}`);
await persistBill(await res.json());
}Related
- Rate Limits — rate limit policy and retry strategies
- Authentication — API key and bearer token auth
- API Reference: Billers — endpoint-specific error details
- Idempotency — webhook dedupe by
event.id - Webhooks — envelope shape and signature verification