Error Codes
Every API error has the same envelope:
{
"success": false,
"error": {
"statusCode": 400,
"code": "INVALID_ANSWER",
"message": "The answer was incorrect, please try again",
"details": { "remaining": 2 }
}
}
statusCode is also returned as the HTTP status. details is optional and depends on the error.
4xx — Client errors
| Code | Status | Meaning |
|---|---|---|
UNSUPPORTED_CHALLENGE | 400 | Unknown challenge type. |
INVALID_DOMAIN | 400 | Malformed domain name. |
INVALID_ANSWER | 400 | Wrong answer; details.remaining shows attempts left. |
CHALLENGE_EXPIRED | 400 | Challenge older than its TTL. |
CHALLENGE_SOLVED | 400 | Challenge already solved. |
CHALLENGE_FAILED | 400 | Too many failed attempts. |
CHALLENGE_NOT_FOUND | 404 | Unknown challenge ID. |
INVALID_SECRET | 400 | Site secret does not match any site. |
TOKEN_SITE_MISMATCH | 400 | Token was issued for a different site. |
INVALID_TOKEN | 400 | Malformed or unsigned token. |
TOKEN_EXPIRED | 400 | Token older than 120 seconds. |
TOKEN_REPLAYED | 400 | Token already redeemed — replay attempts are rejected. |
INVALID_2FA_CODE | 400/401 | Wrong TOTP code. |
TWO_FACTOR_NOT_ENABLED | 400 | 2FA is not enabled for this account. |
INVALID_PASSWORD | 400 | Current password is wrong. |
INVALID_RESET_TOKEN | 400 | Unknown/expired password reset token. |
INVALID_KEY_PREFIX | 400 | Blocked-key input does not start with nlx_. |
UNAUTHORIZED | 401 | Missing authentication. |
INVALID_TOKEN / INVALID_API_KEY / INVALID_REFRESH_TOKEN / INVALID_PREAUTH | 401 | Bad or expired credentials. |
ACCOUNT_DISABLED | 403 | Account deactivated by an admin. |
ADMIN_REQUIRED | 403 | Admin role required. |
DOMAIN_NOT_ALLOWED | 403 | Request hostname is not on the site's domain whitelist. |
DOMAIN_BLOCKED | 403 | Request hostname is on the global blocklist. |
SITE_KEY_BLOCKED | 403 | Site key is on the global blocklist. |
SITE_SUSPENDED | 403 | Site has been suspended by an admin. |
SCOPE_FORBIDDEN | 403 | API key lacks the required scope. |
EMAIL_TAKEN | 409 | Email already registered. |
DOMAIN_EXISTS | 409 | Domain already registered for this site. |
DOMAIN_ALREADY_BLOCKED | 409 | Domain already on blocklist. |
KEY_ALREADY_BLOCKED | 409 | Key already on blocklist. |
NOT_FOUND | 404 | Generic resource not found. |
SITE_NOT_FOUND | 404 | Unknown site. |
DOMAIN_NOT_FOUND | 404 | Domain not registered for this site. |
USER_NOT_FOUND | 404 | Unknown user. |
API_KEY_NOT_FOUND | 404 | Unknown API key. |
RATE_LIMITED | 429 | Too many requests — retry later. |
5xx
| Code | Status | Meaning |
|---|---|---|
INTERNAL_ERROR | 500 | Unexpected server error. |
Handling in the widget
The widget surfaces human-readable messages for the common cases (wrong answer, expired challenge, network error) via onError. Re-verification is automatic after reset().
Handling in your backend
Verify failures must never block a legitimate user permanently. Recommended handling:
if (!result.success || result.score < 0.5) {
// notify the user, do NOT process the form
}