Billium

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"
}
FieldDescription
statusCodeHTTP status code — always matches the response status.
messageHuman-readable description. For validation errors it may be an array, one entry per invalid field.
errorShort class name (Bad Request, Unauthorized, Forbidden, …).

Status codes

StatusMeaningTypical cause
400Validation failedBody missing a required field, value outside its range, malformed JSON.
401Not authenticatedx-api-key header missing or the key is unknown / revoked.
403Not authorizedKey is valid but lacks the required permission, or a pk_* key tried to reach an sk_*-only endpoint.
404Not foundResource does not exist or belongs to a different merchant.
409ConflictIdempotency-Key replayed with a different body. See Idempotency.
422Unprocessable entityBusiness rule rejected the request (e.g. cancelling an already-terminal invoice).
429Too many requestsRate limit exceeded. See Rate limits. Retry-After header tells you when to retry.
500 / 502 / 503 / 504Server errorTransient — 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:

ClassMeaning
BilliumWebhookSignatureErrorHeader malformed, wrong format, or HMAC mismatch. Tampered or wrong secret. Return 400.
BilliumWebhookTimestampErrorTimestamp is outside the tolerance window. Possible replay attack. Return 400.

See Verify signatures for a worked example.

On this page