Developer API errors
All Developer API errors return a JSON object with anerror code and a human-readable message:
Public auth errors (SDK)
The public auth endpoints (/auth/validate, /auth/heartbeat, and /auth/selfban) return errors as real HTTP status codes with a JSON body of the form {"status": "failed", "error": "<code>"}:
Successful responses return
200 with status: "success".
Public auth requests are protected by two layers of throttling:
- API Gateway stage throttling: burst
200, sustained100 req/s(default for every route on this API:/auth/validate,/auth/heartbeat, and/auth/selfban) - Application-level per-minute limits on
/auth/validate:30/minper IP5/minper license key
- Application-level per-minute limit on
/auth/heartbeat:6/minper license key
/auth/heartbeat is not IP rate-limited at the application layer, only per-license. The default online check-in interval of 15 minutes is far below the limit; the per-license cap exists to stop runaway loops.
Unlimited-seat (shared) licenses are limited per device instead of per key, so a popular shared key is not throttled as one bucket: 5/min per device on /auth/validate and 6/min per device on /auth/heartbeat (a device is the license key plus its HWID). Each endpoint also has a ceiling across all devices on the key: 300/min on /auth/validate and 3000/min on /auth/heartbeat. Validate’s is lower because the client chooses the HWID and every success costs a credit, so the per-key ceiling is what bounds a leaked key. These limits use fixed one-minute windows, so a launch rejected by the 300/min validate ceiling will usually be rejected again if retried within the same minute. Apps that run one shared key across a large fleet that may start all at once should retry login() on rate_limited with exponential backoff for up to about 60 seconds.
Additional application limits on /auth/selfban: 10/min per IP (all self-ban requests), and 3/min per license key for pre-session requests that send licenseKey + app credentials.
When a validate request exceeds either limit, the response is HTTP 429 with:
/auth/validate responses include the X-RateLimit-Remaining header (the lowest of the remaining IP and per-license validate quotas; for shared keys, the per-device and per-key quotas). Successful /auth/heartbeat responses include the same header with the remaining per-license check-in quota (for shared keys, the lower of the per-device and per-key quotas). The official SDKs parse both the HTTP status code and the JSON error field, so 429 responses surface as rate_limited automatically; no special handling is needed in your integration.
Validate errors (/auth/validate)
Heartbeat errors (/auth/heartbeat)
The SDK classification column shows what the official SDKs (1.4.0+) do when a check-in fails with each code:
- Definitive: the SDK clears the stored session (as
logout()does) and stops check-ins, then callsonFailure("heartbeat_failed", error). - Transient: the SDK keeps the session, calls
onFailure("heartbeat_failed", error), and checks in again on the next interval. Once the session TTL has passed, the next transient failure is reported as a definitivesession_expired. The SDKs report the grace period ending the same way, except Rust, which reports local session expiry asexpired(AuthForgeError::Expired) until its next major release.
{"status": "failed", "error": "<code>"}); anything else is reported as a transient SDK error (see SDK client-side errors). SDKs 1.4.0+ expose the code and classification on the error passed to onFailure; see Check-in and session failure handling for the per-language API.
invalid_app is also definitive in the SDKs, but /auth/heartbeat does not return it: a deactivated app yields session_expired.
replay_detected is never returned from /auth/heartbeat. Heartbeats do not enforce nonce replay detection; replay protection is provided by the signed, short-lived session token. Heartbeat rate limiting is per license (6/min; per device for unlimited-seat licenses), never per IP.Self-ban errors (/auth/selfban)
SDK client-side errors
In addition to server errors, the SDK may detect issues locally before or after a server response:
These errors trigger the
onFailure callback with the "login_failed" or "heartbeat_failed" reason. Any code an SDK version does not recognize is passed through unchanged and classified as transient.
Offline license file errors (loginFromFile / verifyLicenseFile)
Verifying a .authforge offline license file is entirely local. Every SDK runs the same checks in this order and reports the first failure through onFailure("offline_login_failed", error) (Go and Rust return typed errors instead):
loginFromFile never terminates the process on its own; display the message and exit from your code.
Best practices for error handling
- Never expose internal error codes to end users. Map them to user-friendly messages.
- Log the actual error code for debugging, but show generic messages to users.
- Let the SDK handle transient check-in errors. Network errors,
rate_limited,system_error,no_credits,app_burn_cap_reached, and the other transient codes keep the session, and the SDK checks in again on the next interval. It retriesrate_limited(after 2 and 5 seconds) and network failures (once) internally, but deliberately waits for the next interval onno_credits,demo_quota_exceeded, andapp_burn_cap_reached; don’t add your own retry loop on top. Forlogin(), retrying network failures a few times with backoff is fine; retryingno_creditsis not, since it only clears once you add credits. - Don’t retry definitive errors:
revoked,expired,hwid_mismatch,blocked,session_expired,malformed_request,app_disabled,invalid_app, andsignature_mismatch(at login,invalid_keyis also final for the key the user entered). On a check-in, the SDK has already cleared the session; prompt the user to take action instead. - Handle
no_creditsgracefully: this is a billing issue on your end, not the user’s problem.