AppAIGatewayDocs
Integrate your app

Errors and limits

The error format, every code a client can see, and what to do about each one.

Every refusal from the gateway is JSON with one error object:

{
  "error": {
    "code": "app_rate_limited",
    "message": "Per-user rate limit exceeded",
    "data": { "scope": "user" }
  }
}

code is stable and is what your client should switch on. message is for logs. data is present only when the code alone is not actionable. Every 429 carries a Retry-After header in seconds.

A provider's own error is not in this format: it is passed through as the provider sent it, with the provider's status code. Tell the two apart by whether the body has a top-level error.code string from the table below.

Authentication

CodeStatusMeaningWhat to do
auth_required401 or 403No usable credential, an expired gateway token, a revoked key, or a blocked userExchange a new token. If it persists for one user, they are blocked
attest_failed403The App Attest attestation or assertion did not verify, or the key's environment is no longer acceptedRegister a new key. The Swift package does this once automatically
issuer_token_rejected403The sign-in token did not verifyRefresh the ID token, retry once, then treat as signed out
issuer_claims_missing403The token verified but the required entitlement claim is not there yetWait and retry. Do not sign the user in again. Sync the purchase if you can
issuer_verification_unavailable503The identity provider's keys could not be fetchedRetry with backoff
auth_method_not_supported404The app is not configured for this call, such as a token exchange on an app without user identityFix the app's configuration
invalid_request400A malformed body, a missing X-App-Version, or a missing end-user headerFix the request

Limits

CodeStatusMeaningWhat to do
app_rate_limited429A per-minute or per-day request limit set on this app refused the call. data.scope is user or appWait Retry-After seconds. A user scope means slow this user down; an app scope means the whole app is busy
app_budget_exhausted429A monthly spending budget set on this app is spent. Same data.scopeWait for the next month, or raise the budget in the console
billing_request_quota_exceeded429The monthly request allowance of the account that owns the app is spent. data carries limit, used and resetAtNothing on the device fixes it. Show a friendly message and retry after resetAt
billing_unavailable503The gateway could not check the account, so it refused rather than guessRetry after Retry-After. Not an account problem
billing_payment_required402The account has no plan at allThe account owner has to act
payload_too_large413The request body is over 20 MBSend less

app_* codes come from limits set on the app in the console. billing_* codes come from the account and are unrelated to those limits.

Policy

CodeStatusMeaningWhat to do
path_not_allowed403The app's proxy policy does not allow this provider or pathUse an allowed path, or allow it in the console
model_not_allowed403The model is not in the app's allowlistUse an allowed model
max_output_tokens_exceeded403The request asks for more output than the app allowsLower the output limit in the request
api_style_not_supported403This path does not exist on the route the provider uses, such as Gemini's native API through VercelUse a supported path or a direct provider
pricing_not_configured400The model has no price in the catalog or the provider's custom pricingAdd a price in the console, or use a catalogued model
endpoint_not_found404No named endpoint has this slugCheck the slug
app_not_found404No app has this IDCheck the app ID
app_disabled403The app is switched off in its settingsNothing on the device fixes it

Providers

CodeStatusMeaningWhat to do
provider_not_configured502No provider has this slugAdd one in the console
provider_disabled502The provider with this slug is pausedEnable it in the console
provider_unavailable502The provider's key could not be readCheck the gateway's health
provider_error502 or 504The provider could not be reached, or sent nothing for 120 secondsRetry with backoff
internal_error500Something failed inside the gatewayRetry with backoff

A sensible client policy

  • Treat 429 and 5xx as retryable, everything else as something to fix.
  • On app_rate_limited with scope user, back that user off for Retry-After seconds. With scope app, show a "busy, try again shortly" state rather than hammering.
  • On issuer_claims_missing, keep the user signed in and poll gently.
  • Send X-App-Version on every request, so the console can tell you which release a problem belongs to.
  • Log code and message, never the request body or the token.

The Swift package's GatewayError exposes code, retryAfter, limitScope and isRetryable for exactly this. See iOS with the Swift package.

On this page