2xx means the request worked. A 4xx means something in the request needs fixing. A 5xx means something went wrong on our side.
Error format
Every error has the same shape.{
"error": {
"type": "not_found",
"code": "customer_not_found",
"message": "There is no customer with that code. To create one in Xero, send `create_customer`.",
"param": "customer_code",
"request_id": "req_4f1a9c2e7b8d3a605e1c9f02"
}
}
| Field | Description |
|---|---|
type | The family of error. It decides the HTTP status. |
code | A stable, machine-readable string. Branch on this. |
message | A sentence for a person. It can be reworded at any time, so never match on it. |
param | The request field the error is about. Present on most 422 and some 404 and 409 errors. Nested fields use dots, such as create_customer.name. |
request_id | The same value as the Request-Id header. Quote it when you contact support. |
Error types
| Type | Status | Meaning |
|---|---|---|
authentication_error | 401 | The API key is missing, malformed, revoked or expired. |
permission_error | 403 | The key is valid but isn’t allowed to do this. |
invalid_request_error | 422 | A field is missing or invalid. Also 400 for a malformed body and 413 for an oversized one. |
not_found | 404 | No such object, or no such endpoint. |
conflict | 409 | The request clashes with the current state, or with an earlier request that used the same Idempotency-Key. |
rate_limit_error | 429 | Too many requests. |
api_error | 500 | Something went wrong on our side. |
Error codes
Authentication and permission
| Status | Code | Meaning |
|---|---|---|
401 | invalid_api_key | The Authorization header is missing or malformed, or the key doesn’t exist. |
401 | api_key_revoked | The key has been revoked. |
401 | api_key_expired | The key has expired. |
403 | insufficient_scope | The key lacks the scope this endpoint needs. |
403 | sandbox_not_available | A saasybill_test_ key was used. Test keys can’t be used yet. |
403 | xero_contact_creation_disabled | The request asked to create a Xero contact, but the organisation hasn’t allowed it. See Customers and Xero. |
403 | plan_limit_reached | Creating this would exceed a limit on the organisation’s Saasybill plan, such as its customer or subscription limit. |
Invalid requests
| Status | Code | Meaning |
|---|---|---|
422 | invalid_parameter | A field is missing or invalid. param names it. |
400 | invalid_json | The body isn’t valid JSON. |
413 | payload_too_large | The body is over 100,000 characters. |
422 | idempotency_key_required | The request needs an Idempotency-Key header. |
422 | idempotency_key_invalid | The key isn’t 1 to 255 printable characters. |
Unit changes
Unit change errors are422, and param names the field. The code is one of these.
| Code | Meaning |
|---|---|
units_invalid | units isn’t a whole number of zero or more. |
units_below_minimum | units is under the minimum set on the plan or the subscription. |
units_unchanged | The subscription already has this many units. |
plan_has_no_units | The plan is a flat fee plan, which doesn’t bill by units. |
effective_date_required | prorate is true but effective_date is missing. |
effective_date_out_of_period | effective_date is outside the current billing period. |
proration_required | This change lowers the amount payable and raises a credit note, so prorate can’t be false. |
proration_behaviour_not_applicable | proration_behaviour was sent without prorate: true. |
setup_mode_on | The organisation is in Setup Mode, where subscriptions can’t be updated. |
subscription_not_changeable | The subscription has ended or completed. |
document is missing, or names a choice that isn’t available. The error message lists the choices. See Change units.
Not found
| Status | Code | Meaning |
|---|---|---|
404 | customer_not_found | No customer has that id or code. |
404 | plan_not_found | No plan has that id or code. |
404 | charge_not_found | No charge has that id. |
404 | subscription_not_found | No subscription has that id. |
404 | invoice_not_found | No invoice has that id. |
404 | unknown_endpoint | The path isn’t part of the API. |
Conflicts
| Status | Code | Meaning |
|---|---|---|
409 | idempotency_conflict | The Idempotency-Key was already used with a different request. |
409 | request_in_progress | A request with this Idempotency-Key is still running. Try again shortly. |
409 | invalid_state | The subscription can’t be ended or restored right now. The message says why. |
409 | xero_accounts_not_mapped | The organisation hasn’t mapped its Xero accounts. An admin does this on the Integrations tab. |
409 | xero_not_connected | The organisation isn’t connected to Xero. |
409 | xero_contact_name_taken | Xero already has a contact with that name. Use that contact’s code, or a different name. |
409 | xero_contact_archived | The Xero contact with that code is archived. Restore it in Xero, or use another code. |
409 | customer_not_importable | A Xero contact has that code but hasn’t been imported as a customer. Import it on the Customers page in Saasybill. |
409 | customer_not_created | The contact exists in Xero but couldn’t be added as a customer. |
Server errors
| Status | Code | Meaning |
|---|---|---|
429 | rate_limited | Over the rate limit. Wait for Retry-After seconds. |
500 | internal_error | Something went wrong on our side. |
502 | xero_error | Xero refused or failed a request the API made on your behalf. The message says why. |
New codes can be added to any family. Handle the ones you care about, and treat the rest by their status.
Handling errors
| Status | What to do |
|---|---|
400, 422 | Fix the request. Read param to find the field. Don’t retry unchanged. |
401 | Check the key. Don’t retry. |
403 | The key or the organisation isn’t set up for this. Don’t retry. |
404 | The object doesn’t exist. Check the id or code. |
409 | Read code. For request_in_progress, wait and retry. For the rest, resolve the state and try again. |
429 | Wait for the Retry-After header, then retry. |
500, 502 | Retry with backoff, using the same Idempotency-Key. If it keeps failing, contact support with the request_id. |
A write that times out may or may not have happened. Retry it with the same
Idempotency-Key and Saasybill either completes it or returns the first answer. It never does the work twice. See Idempotency.