Errors
Error envelope, status codes, and how the Node SDK surfaces them.
Every non-2xx response from the Billium API follows the same JSON envelope.
{
"statusCode": 400,
"message": "rawAmount must be less than or equal to 1000000",
"error": "Bad Request"
}| Field | Description |
|---|---|
statusCode | HTTP status code — always matches the response status. |
message | Human-readable description. For validation errors it may be an array, one entry per invalid field. |
error | Short class name (Bad Request, Unauthorized, Forbidden, …). |
Status codes
| Status | Meaning | Typical cause |
|---|---|---|
400 | Validation failed | Body missing a required field, value outside its range, malformed JSON. |
401 | Not authenticated | x-api-key header missing or the key is unknown / revoked. |
403 | Not authorized | Key is valid but lacks the required permission, or a pk_* key tried to reach an sk_*-only endpoint. |
404 | Not found | Resource does not exist or belongs to a different merchant. |
409 | Conflict | Idempotency-Key replayed with a different body. See Idempotency. |
422 | Unprocessable entity | Business rule rejected the request (e.g. cancelling an already-terminal invoice). |
429 | Too many requests | Rate limit exceeded. See Rate limits. Retry-After header tells you when to retry. |
500 / 502 / 503 / 504 | Server error | Transient — retry with backoff. Open an issue if it persists. |
SECURITY_REQUIREMENT_NOT_MET
A 403 response with this error code means the team you're acting on has enforced a security factor (2FA, passkey, Google/GitHub login, or verified email) that your account does not currently satisfy. The response adds a missing[] array so you know exactly what's blocking the request:
{
"statusCode": 403,
"error": "SECURITY_REQUIREMENT_NOT_MET",
"message": "Your account does not meet the security requirements for this team",
"missing": ["tfa", "passkey"]
}Possible missing values: tfa, passkey, google_login, github_login, email_verified.
This check only applies to dashboard JWT sessions — API-key requests (sk_*, pk_*) are not subject to team-level enforcement. See Team security enforcement for how owners configure these requirements.
Handling errors with the Node SDK
The Node SDK raises BilliumApiError on every non-2xx response. The error carries the HTTP status, the original message, and an optional code.
import { BilliumApiError } from '@billium/node';
try {
await billium.invoices.create({ /* ... */ });
} catch (err) {
if (err instanceof BilliumApiError) {
switch (err.status) {
case 400:
// validation — show the user their bad input
break;
case 401:
case 403:
// auth — your API key is wrong or lacks permission
break;
case 409:
// idempotency conflict — same key, different body
break;
case 429:
// backoff (the SDK already retries automatically)
break;
default:
throw err;
}
}
}The SDK automatically retries 429, 500, 502, 503, and 504 with exponential backoff (full jitter, Retry-After honored). You only need to handle the error after retries are exhausted.
Webhook errors
Webhook signature failures surface as two distinct error classes so you can react differently:
| Class | Meaning |
|---|---|
BilliumWebhookSignatureError | Header malformed, wrong format, or HMAC mismatch. Tampered or wrong secret. Return 400. |
BilliumWebhookTimestampError | Timestamp is outside the tolerance window. Possible replay attack. Return 400. |
See Verify signatures for a worked example.