Every error returns JSON: { "success": false, "error": { "code": "...", "message": "..." } }. Branch on the code, never on the message text.
| Code | HTTP | Meaning | Handling |
|---|---|---|---|
LICENSE_NOT_FOUND | 404 | The key does not exist or was deleted | Ask the user to check the key; do not auto-retry |
LICENSE_EXPIRED | 403 | The license expired | Offer renewal; keep read-only features |
LICENSE_SUSPENDED | 403 | The license is suspended | Ask the user to contact the owner |
LICENSE_REVOKED | 403 | The license was revoked | Lock paid features |
ACTIVATION_LIMIT | 409 | No device slot left | Send the user to release a device |
DEVICE_HELD | 409 | The device is held or migrating | Retry later or contact support |
SEAT_PAUSED | 403 | The member is paused over the seat limit | Add a seat or remove a member |
FLOATING_LICENSE | 409 | A floating license needs a session first | Call floating checkout |
RATE_LIMITED | 429 | Too many requests | Exponential backoff; do not retry in parallel |
INVALID_IDEMPOTENCY_KEY | 400 | Invalid idempotency key or reused with a different body | Reuse the same key per retry |
UNAUTHORIZED | 401 | No session for portal/admin routes | Sign in again or refresh the session |
LICENSE_KEY_REQUIRED | 401 | A gated release feed needs a license | Send the license key or token |
Retry strategy
- Retry 5xx, timeouts and 429. Do not blindly retry 4xx business errors.
- Use the same Idempotency-Key when retrying activation.
- Exponential backoff with jitter, starting at 1s and capped at 30s.
- While offline, trust the local token and check in when the network returns.