Skip to content

Error Codes Reference

Document Version: 1.1.0 Last Updated: 2026-10-15 Applicable Version: v3.3.3+ Author: AI Assistant

JAiRouter uses a standardized error code system to identify various error conditions. All error responses include an error code field for client-side error handling.

⚠️ The response shape depends on the entry surface: /api/** (console) uses the RouterResponse shape shown below (errorCode field); /v1/** (OpenAI surface) uses a nested {"error":{"message","type","code"}} (see Universal API); /v1/messages (Anthropic surface) uses Anthropic's {"type":"error","error":{...}} shape. Two known exceptions do not use this table: the admin rate limiter returns a flat {"code":"RATE_LIMIT_EXCEEDED","message":...} body with 429, and exception-handler fallbacks return numeric codes (for example {"code":"500"}).

📌 Code is the authority for this table: it is reconciled entry by entry against common/constants/ErrorCode.java (the error-code registry, 62 codes that are actually emitted), and scripts/docs/check-error-codes.py verifies it both ways in CI — a registry code missing from this table, or a table code that can never be emitted, fails the build. Which chain decides each code's HTTP status is registered too (its render path), so change the registry before changing a code name or status.

Error Response Format

{
  "success": false,
  "message": "Error description",
  "errorCode": "ERROR_CODE",
  "data": null
}

HTTP Status Code Mapping

HTTP StatusDescriptionError Category
400Bad RequestValidation Error
401UnauthorizedAuthentication Error
403ForbiddenAuthorization Error
404Not FoundResource Error
409ConflictBusiness Error
429Too Many RequestsRate Limit Error
500Internal Server ErrorSystem Error
502Bad GatewayDownstream Error
503Service UnavailableDownstream Error

Authentication Error Codes

HTTP Status: 401 Unauthorized

The codes in this section are carried by authentication exceptions (their render path in the registry is the authentication chain): the client-visible status comes from the exception's 401, and is not derived from the code's naming rule — INVALID_API_KEY, for example, would resolve to 400 by rule, but clients receive 401.

Error CodeDescriptionCommon CauseSolution
INVALID_API_KEYInvalid API KeyKey missing, malformed, caller IP not allowlisted, or the key is disabledCheck the key and where it is called from
EXPIRED_API_KEYAPI Key expiredThe key exceeded its validity periodRegenerate the API Key
EMPTY_API_KEYNo API Key carriedThe request is missing the X-API-Key headerAdd the X-API-Key header
API_KEY_DISABLEDAPI Key authentication is disabledThe server turned API Key auth offEnable it on the server
API_KEY_AUTH_FAILEDAPI Key authentication failedThe validation chain threw an unexpected exceptionCheck server logs for the root cause
DAILY_QUOTA_EXCEEDEDDaily request quota exceededThe key used up its daily requestsWait for the daily reset or raise the quota
TOKEN_QUOTA_EXCEEDEDDaily token quota exceededThe key used up its daily tokensWait for the daily reset or raise the quota
RATE_LIMIT_EXCEEDEDRequest rate exceededThe key exceeded its per-minute limit (see the dual status note in "Rate Limit Error Codes")Back off and retry
JWT_INVALIDJWT validation failedSignature mismatch or malformed tokenSign in again to obtain a new token
JWT_EXPIREDJWT token expiredThe token exceeded its validity periodRefresh it or sign in again
EXPIRED_JWT_TOKENJWT token expired (classified as expired)The authentication manager classifies expired failures with this codeSign in again
JWT_BLACKLISTEDToken is blacklistedThe token was revoked or logged outSign in again to obtain a new token
JWT_VALIDATION_ERRORError while validating the JWTThe validation chain threwCheck server logs
JWT_REFRESH_EXPIREDJWT is past its refresh windowThe refresh window has elapsedSign in again
JWT_REFRESH_ERRORError while refreshing the JWTThe refresh chain threwCheck server logs
JWT_REFRESH_FAILEDJWT refresh failedThe refresh chain threwSign in again
JWT_USER_ID_EXTRACTION_FAILEDCannot extract a user id from the JWTThe token carries no user identifier the gateway can readSign in again
JWT_AUTH_FAILEDJWT authentication failedThe authentication manager threw an unexpected exceptionCheck server logs
JWT_DISABLEDJWT authentication is disabledThe server turned JWT auth offEnable it on the server
TOKEN_REQUIREDThe current token is required for refreshThe refresh request carries no current tokenSend the current token with the refresh
EMPTY_JWT_TOKENNo JWT carriedThe request is missing the Jairouter_Token headerAdd the Jairouter_Token header
AUTH_MISSINGNo credentials carriedNeither an API Key nor a JWT was suppliedSupply credentials
AUTH_FAILEDAuthentication failed (default code)The authentication filter's fallbackCheck server logs for the concrete cause
AUTH_ERRORUnknown error during authenticationThe authentication chain threw an unrecognized exceptionCheck server logs
UNSUPPORTED_AUTH_TYPEUnsupported authentication typeThe client used a credential type this gateway does not recognizeUse an API Key or a JWT
LOGIN_FAILEDLogin failedWrong username or passwordCheck the login credentials

Example Response:

{
  "success": false,
  "message": "Invalid API Key",
  "errorCode": "INVALID_API_KEY",
  "data": null
}


Authorization Error Codes

HTTP Status: 403 Forbidden

Error CodeDescriptionCommon CauseSolution
FORBIDDENForbiddenGeneric authorization failureCheck the role and permissions
FORBIDDEN_TOKEN_USAGENot allowed to view this token's usageThe token does not belong to the callerQuery it with the owning account

⚠️ INSUFFICIENT_PERMISSIONS / ACCESS_DENIED / FORBIDDEN_RESOURCE are declared in AuthorizationException but have no emission site anywhere in the repository (their factory methods are only called by unit tests) ⇒ they were removed from this table: a client branching on them would never match. Real 403s come from the Spring Security authorization chain and from FORBIDDEN above.


Resource Error Codes

HTTP Status: 404 Not Found

Error CodeDescriptionCommon CauseSolution
NOT_FOUNDResource not foundThe requested resource does not existCheck the resource id or path
TEMPLATE_NOT_FOUNDTemplate not foundUnknown template idCheck the template id
TOKEN_NOT_FOUNDToken not foundUnknown token idCheck the token id
ADAPTER_NOT_FOUNDAdapter not foundUnknown adapter nameCheck the adapter name

ApiException.resolveStatus also maps any unregistered code ending in _NOT_FOUND to 404 by rule; the table above lists the four registered codes.


Validation Error Codes

HTTP Status: 400 Bad Request

Error CodeDescriptionCommon CauseSolution
INVALID_REQUESTInvalid request dataParameter format or value errorCheck the request parameters
INVALID_PARAMInvalid parameterParameter value out of rangeCheck the parameter constraints
INVALID_PAGEInvalid page parameterNegative page numberSend a valid page number
INVALID_SIZEInvalid page sizePage size outside 1-100Send a valid page size
INVALID_TOKEN_IDInvalid token idEmpty or malformed token idCheck the token id
INVALID_TYPEInvalid typeThe type value is not in the enumCheck the type value
INVALID_NAMEInvalid nameMissing or blank nameSend a non-blank name
INVALID_PERMISSIONInvalid permission entryThe permission code is not in the permission listUse a registered permission code
INVALID_RECORD_LEVELInvalid record levelThe level is not in the enumUse METADATA_ONLY / SUMMARY / FULL
INVALID_ROLEInvalid role nameThe role is not a known roleUse a known role name
BUILTIN_READONLYBuilt-in entry is read-onlyAttempting to modify or delete a built-in adapterCreate a custom adapter instead
RESTART_REQUIREDA restart is required for these fieldsFields that cannot be hot-reloaded were submittedRemove them, edit the config file and restart

⚠️ BUILTIN_READONLY and RESTART_REQUIRED are returned with 400 by their endpoints (ResponseEntity.badRequest()), while code-name resolution would derive 403 — the divergence is tracked as a pending decision (issue #405) and this table registers the observed values. The previous version also listed VALIDATION_ERROR (only the ValidationFailureType enum constant, never emitted as a response code) and INVALID_PARAMETER / MISSING_PARAMETER / INVALID_FORMAT (no match in the codebase); all four were removed.


Business Error Codes

HTTP Status: 409 Conflict

Error CodeDescriptionCommon CauseSolution
CONFLICT_STATEResource conflictThe resource state does not allow this operationCheck the resource state
CONFLICT_EXISTSResource already existsA resource with the same name existsRename it or switch to an update
CONFLICT_BUILTINConflicts with a built-in entryAttempting to overwrite a built-in entryPick another name

Conflicts surface as CONFLICT_STATE (mapped from exceptions) plus the per-endpoint variants that share the prefix — CONFLICT_EXISTS / CONFLICT_BUILTIN; there is no bare CONFLICT. ApiException.resolveStatus maps every CONFLICT* code to 409.


Rate Limit Error Codes

HTTP Status: 429 Too Many Requests (admin surface) · 401 (API Key validation chain)

Error CodeDescriptionResponse HeaderSolution
RATE_LIMIT_EXCEEDEDRate limit exceededRetry-After / X-Quota-* (admin surface) / none (validation chain)Wait for the bucket to recover and retry

⚠️ One code, two statuses on two real paths (issue #404, pending decision): ① the admin rate limiter (AdminApiRateLimiter) writes a flat 429 body {"code":"RATE_LIMIT_EXCEEDED","message":...}; ② the API Key validation chain (ApiKeyValidator → FullValidationResult.toException()) wraps the same code in an AuthenticationException, so the client receives 401. A client branching on status must handle both.

There is no X-RateLimit-Reset / X-RateLimit-Remaining header anywhere in the codebase; quota rejections write Retry-After and the X-Quota-* family (limit / remaining / window). GLOBAL_RATE_LIMIT / SERVICE_RATE_LIMIT / INSTANCE_RATE_LIMIT were removed from this table: the first two have no match in the codebase, and the third is only a JPA table name.


System Error Codes

HTTP Status: 500 Internal Server Error

Error CodeDescriptionCommon CauseSolution
INTERNAL_ERRORInternal server errorUnexpected exceptionCheck the service logs
ADD_FAILEDAdd operation failedAdding a blacklist entry (or similar) failedCheck the service logs
BATCH_TOKEN_REVOKE_FAILEDBatch token revocation failedThe batch revoke path threwCheck the service logs
BLACKLIST_STATS_FAILEDFailed to read blacklist statisticsThe statistics query threwCheck the service logs
CLEANUP_FAILEDCleanup failedThe cleanup operation threwCheck the service logs
GET_CLEANUP_STATS_FAILEDFailed to read cleanup statisticsThe statistics query threwCheck the service logs
GET_TOKENS_FAILEDFailed to list tokensThe list query threwCheck the service logs
GET_TOKEN_DETAILS_FAILEDFailed to read token detailsThe detail query threwCheck the service logs
TOKEN_REFRESH_FAILEDToken refresh failedThe refresh path threwSign in again
TOKEN_REVOKE_FAILEDToken revocation failedThe revoke path threwCheck the service logs
SANITIZATION_FAILEDData sanitization failedThe sanitization process threwCheck the logs for details
INVALID_SANITIZATION_RULEInvalid sanitization ruleBad rule configurationCheck the rule configuration
CONTENT_PROCESSING_FAILEDContent processing failedUnsupported content formatCheck the content type

⚠️ RULE_COMPILATION_FAILED belongs to the same family as the three authorization codes above: it is declared in SanitizationException and has no emission site (its factory method is only called by unit tests), so it was removed from this table. A rule compilation failure currently surfaces as SANITIZATION_FAILED or INVALID_SANITIZATION_RULE. CONFIGURATION_ERROR / DATABASE_ERROR / CACHE_ERROR from the previous version have no match either: configuration, database and cache failures all surface as INTERNAL_ERROR (which is also the resolver's fallback for any code that matches none of its rules).


Service Unavailable

HTTP Status: 503 Service Unavailable

Error CodeDescriptionCommon CauseSolution
SERVICE_NOT_AVAILABLEService unavailableA required persistence/cleanup service is not enabledCheck the server configuration and retry

Downstream Service Error Codes

HTTP Status: the downstream status verbatim (commonly 502/503/504; a downstream 401 is normalized onto the 401 authentication branch)

Error CodeDescriptionCommon CauseSolution
DOWNSTREAM_SERVICE_ERRORDownstream service errorThe upstream model service failed or timed outCheck the downstream service and the network

This code's HTTP status is not fixed: SecurityExceptionHandler writes the error body with the downstream status verbatim ⇒ the registry records its status as context-dependent (it is not one of 502/503). The previous version listed 5xx / 502 / 503 / 504 as "error code patterns"; those are neither codes nor values this gateway emits.


Error Handling Best Practices

1. Client-Side Error Handling

// JavaScript Example
async function handleApiResponse(response) {
  const data = await response.json();

  if (!data.success) {
    const code = data.errorCode ?? data.error?.code;

    switch (code) {
      case 'INVALID_API_KEY':
      case 'EXPIRED_API_KEY':
      case 'EMPTY_API_KEY':
      case 'JWT_EXPIRED':
      case 'JWT_INVALID':
      case 'JWT_BLACKLISTED':
        // Re-authentication required
        window.location.href = '/login';
        break;
      case 'RATE_LIMIT_EXCEEDED':
        // Back off and retry (the admin surface sends Retry-After with its 429)
        await sleep((Number(response.headers.get('Retry-After')) || 1) * 1000);
        return retryRequest();
      default:
        showError(data.message ?? data.error?.message);
    }
    return;
  }

  return data.data;
}

2. Server-Side Error Handling

// Java Example
@RestControllerAdvice
public class ErrorHandler {

    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<ErrorResponse> handleAuthException(AuthenticationException ex) {
        // The status comes from the exception (the authentication render path in the registry)
        return ResponseEntity.status(ex.getHttpStatus())
            .body(new ErrorResponse(ex.getMessage(), ex.getErrorCode()));
    }

    @ExceptionHandler(ApiException.class)
    public ResponseEntity<ErrorResponse> handleApiException(ApiException ex) {
        return ResponseEntity.status(ex.getStatus())
            .body(new ErrorResponse(ex.getMessage(), ex.getErrorCode()));
    }
}

3. Error Logging

// Log error information
log.error("Authentication failed: errorCode={}, message={}",
    ex.getErrorCode(), ex.getMessage());

Changelog

VersionDateChanges
1.1.02026-10-15Rewritten against the error-code registry (ErrorCode, 62 emitted codes): added the 33 missing codes, removed 7 codes that can never be emitted, documented each render path and the two "endpoint returns 400 while code-name resolution derives 403" cases, and registered DOWNSTREAM_SERVICE_ERROR
1.0.22026-06-30Rewrote both tables from the code (issues #342/#348): dropped 17 non-existent codes, added the codes the authentication chain actually emits
1.0.12026-06-29Update version reference to v2.7.5+, fix incorrect version number
1.0.02026-05-25Initial version with all error code definitions

Last updated: 2026-10-15