Error codes
Error codes
When the e-bon API rejects a request it returns a non-2xx HTTP status and a JSON body with a stable error.code. This page lists every code you can receive, what it means, and how to recover.
Use it when you need to:
- Look up an unfamiliar
error.codereturned by the API. - Decide whether a failed request is safe to retry.
- Build user-facing error handling in your POS, mobile app, or back-office integration.
Understand the error envelope
Every error response uses the same JSON shape:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "path": "body.amount", "message": "Expected number, received string" }
]
}
}
code— a stable identifier from the list below. Branch your client logic on this value, not on the human message.message— a short English description, intended for logs and support tickets. Translate or rewrite it before showing it to end users.details— optional. Always present forVALIDATION_ERRORas an array of{ path, message }pairs. May appear onBAD_REQUEST,CONFLICT, andUNPROCESSABLE_ENTITYwith route-specific data.
{ "code", "message", "status" } at the top level instead of being wrapped in { "error": { ... } }. See Retry rate-limited requests.Handle authentication errors
UNAUTHORIZED — 401
You receive this when the request has no valid credential — the Authorization: Bearer <token> header or the x-api-key header is missing, malformed, has an invalid signature, or the token has expired.
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing or invalid Authorization header. Expected: Bearer <token>"
}
}
How to handle it:
- For JWT clients, refresh your access token via
POST /api/v1/auth/refreshand retry the request once. - For API-key clients, check that the key still exists and is active in the Portal API keys page. Rotate the key if it has been disabled.
- After a successful refresh or rotation, the original request is safe to retry.
FORBIDDEN — 403
The credential is valid but it does not have permission for the requested action — the JWT user lacks the required role, or the API key is missing the required scope.
{
"error": {
"code": "FORBIDDEN",
"message": "Insufficient role. Required: admin or owner"
}
}
How to handle it:
- Do not retry with the same credential — it will keep failing.
- For JWT clients, sign in as a user with the required role.
- For API keys, mint a new key with the required scopes from the Portal. See Authentication API for the full scope list.
Fix validation errors
VALIDATION_ERROR — 400
The request body, query string, or headers did not match the schema for the endpoint. The details array always lists each invalid field and why it was rejected.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "path": "body.items.0.quantity", "message": "Expected number, received string" },
{ "path": "body.paymentType", "message": "Invalid enum value." }
]
}
}
How to handle it:
- Walk
details[*].pathto locate each invalid field in your request payload. - Display the field-level messages next to the corresponding form inputs in your UI.
- After fixing every field, the request is safe to retry.
A common variant: an invalid Idempotency-Key header (must be 1–128 characters of letters, digits, -, and _) returns VALIDATION_ERROR with the offending value in details. See Idempotency and retries.
BAD_REQUEST — 400
The payload is structurally valid but breaks a semantic rule that schema validation cannot express — for example, two mutually exclusive fields supplied together.
{
"error": {
"code": "BAD_REQUEST",
"message": "Cannot specify both 'startTime' and 'cursor'."
}
}
How to handle it: Read message, adjust the request, and retry.
Resolve resource errors
NOT_FOUND — 404
The resource ID in the request does not exist (or is not visible to the current credential).
{
"error": {
"code": "NOT_FOUND",
"message": "Device dev_abc123 not found."
}
}
How to handle it: Verify the resource ID. Do not retry with the same ID — list the parent collection to discover a valid one.
CONFLICT — 409
The request collides with the current state of the resource. Common causes:
- An idempotency-key replay with a different request body.
- A device already claimed by another organization.
- A unique field (email, slug, code) already in use.
{
"error": {
"code": "CONFLICT",
"message": "Device dev_abc123 is already claimed by another organization."
}
}
How to handle it: Read message to identify the specific conflict. Retrying without resolving the underlying state will fail again — release the resource, change the duplicate field, or generate a fresh idempotency key as appropriate.
UNPROCESSABLE_ENTITY — 422
The payload parses correctly but breaks a business rule — for example, receipt line totals that do not add up to the declared total, or a refund amount larger than the original transaction.
{
"error": {
"code": "UNPROCESSABLE_ENTITY",
"message": "Receipt total does not match the sum of line items.",
"details": { "expected": 119.0, "received": 120.0 }
}
}
How to handle it: Use message and details to fix the business-rule violation, then resubmit.
Retry rate-limited requests
RATE_LIMIT_EXCEEDED — 429
You exceeded the per-key (or per-IP) request budget. The response includes a Retry-After header in seconds.
HTTP/1.1 429 Too Many Requests
Retry-After: 47
Content-Type: application/json
{
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests, please try again later.",
"status": 429
}
How to handle it:
- Wait for the number of seconds in
Retry-Afterbefore retrying. - Add jitter when multiple clients share a key, to avoid synchronised bursts.
- For sustained throughput above your current limit, contact support to raise the per-key budget rather than retrying through it.
{ "error": { ... } }. Read code from the top level for this status only.Handle plan limits
TIER_LIMIT_EXCEEDED — 403
A subscription-plan quota is exhausted — typically the monthly receipt cap on the current plan.
{
"error": {
"code": "TIER_LIMIT_EXCEEDED",
"message": "Monthly receipt quota exceeded for the Starter plan."
}
}
How to handle it:
- Surface a clear upgrade prompt to the account owner.
- Direct them to Billing and subscriptions to change plan.
- Quotas reset at the start of each billing period — operations resume automatically once the window rolls over.
Recover from server errors
INTERNAL_ERROR — 500
Something went wrong on the e-bon side that is not classified as a more specific error.
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal error occurred"
}
}
How to handle it:
- Capture the
X-Request-Idresponse header — it identifies the failed request in our logs. - Retry with exponential backoff and jitter (do not retry tightly).
- If the error persists, open a support ticket and include the
X-Request-Idvalue. See Request tracing and logging.
SERVICE_UNAVAILABLE — 503
A downstream dependency is temporarily unavailable. The most common case is a fiscal device that is offline or has no controller assigned. Cloud-storage outages (for example logo uploads) also surface here.
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Device is offline or has no controller assigned"
}
}
How to handle it:
- Check the device connection status in the Portal or via the Devices API.
- Retry with exponential backoff and jitter — 503 responses do not include a
Retry-Afterheader. - Inform the operator if the device stays offline so they can power-cycle or reconnect it.
Map HTTP status to error code
Use this table when writing proxy, WAF, or observability rules. Branch on error.code (not status) when two codes share a status.
| HTTP status | Codes that use it |
|---|---|
| 400 | VALIDATION_ERROR, BAD_REQUEST |
| 401 | UNAUTHORIZED |
| 403 | FORBIDDEN, TIER_LIMIT_EXCEEDED |
| 404 | NOT_FOUND |
| 409 | CONFLICT |
| 422 | UNPROCESSABLE_ENTITY |
| 429 | RATE_LIMIT_EXCEEDED |
| 500 | INTERNAL_ERROR |
| 503 | SERVICE_UNAVAILABLE |
Where to next
- Authentication API — endpoints that issue and refresh JWTs.
- Authentication architecture — the JWT and API-key model behind 401 and 403 responses.
- Idempotency and retries —
Idempotency-Keyrules and retry semantics. - Request tracing and logging — the
X-Request-Idheader to quote in support tickets. - Billing and subscriptions — per-plan quotas behind
TIER_LIMIT_EXCEEDED. - SDK errors — the client-side
ApiErrorclass hierarchy that wraps this envelope. - Glossary — terminology used throughout the docs.
Status values in receipts, devices and webhooks
Reference for every status value e-bon returns in API responses, webhook payloads and the Portal — what each value means and when you'll see it.
Locations
Manage every shop, restaurant or office in one place — add locations, assign devices, compare performance and give each manager scoped access from the e-bon Portal.