Error handling
{ "error": { "code": "insufficient_scope", "message": "Token lacks scope products:write", "requestId": "4b9e…" } }
Quote requestId when you contact support.
| HTTP | code |
Meaning | Retry? |
|---|---|---|---|
| 400 | validation_failed |
Body/query invalid; details lists { path, message } |
No — fix the request |
| 400 | bad_request |
Semantically wrong request | No |
| 401 | unauthenticated |
Missing, expired or revoked token | Refresh / re-authorise once |
| 401 | mfa_required |
Dashboard sessions only | — |
| 402 | payment_required |
A card/EFT payment was not confirmed | No |
| 402 | plan_limit_reached |
Business plan limit (registers, staff, products…) | No |
| 403 | forbidden |
Not allowed (role, suspended business, token on a dashboard-only route) | No |
| 403 | insufficient_scope |
Token lacks the scope | No — request the scope |
| 404 | not_found |
Missing or not yours (indistinguishable by design) | No |
| 409 | conflict |
Unique clash (SKU, barcode), already done, ledger integrity rule | No |
| 409 | idempotency_conflict |
Same Idempotency-Key, different body |
No |
| 409 | period_closed |
Posting into a closed accounting period | No |
| 422 | unprocessable |
Valid shape, impossible request (unknown variant, totals mismatch, over-refund) | No |
| 429 | rate_limited |
Slow down; honour retry-after |
Yes |
| 5xx | internal_error |
Our fault | Yes, with backoff and the same idempotency key |
OAuth endpoints use RFC 6749 errors: { "error": "invalid_grant", "error_description": "…" }.
Retrying safely
Retry only 429, 5xx and network errors, with exponential backoff (250 ms × 2ⁿ, capped) plus jitter, and the same idempotency key each time. The SDK's TechturPosError.retryable tells you which errors qualify.