Real-world examples of authentication error handling in API responses

If you work with APIs long enough, you realize that **examples of authentication error handling in API responses** are worth far more than abstract theory. Teams don’t just want to know that 401 and 403 exist; they want to see how Stripe, GitHub, and Google actually structure their JSON, what error codes they return, and how they communicate retry logic or security risks. Good authentication error handling is the difference between a developer saying “oh, I see the problem” and a production outage ticket. In this guide, we’ll walk through real examples of authentication error handling in API responses from widely used platforms, then break down patterns you can safely copy in 2024–2025. We’ll talk about status codes, response bodies, rate limits, OAuth quirks, and security tradeoffs. The goal is simple: after reading this, you should be able to design API error responses that are predictable, safe, and developer-friendly—without leaking sensitive information or confusing clients.
Written by
Jamie
Published
Updated

Real examples of authentication error handling in API responses

Let’s start where most developers actually learn: by reading other people’s APIs. These examples of authentication error handling in API responses show how mature platforms structure their messages.

Example of a simple 401 Unauthorized (missing token)

A classic pattern: the client forgets to send an Authorization header. Many APIs respond like this:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example-api", error="invalid_token"
Content-Type: application/json

{
  "error": "unauthorized",
  "error_description": "Missing or invalid access token.",
  "error_code": "AUTH_001"
}
``

Why this works:

- HTTP status 401 clearly signals authentication failure.
- `WWW-Authenticate` header follows [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750), which many HTTP clients understand.
- The JSON body is machine-readable (`error_code`) and human-readable (`error_description`).

Patterns you can copy from this **example of** authentication error handling:

- Always send a clear, stable `error_code` for programmatic handling.
- Keep `error_description` short, specific, and in plain English.

### Example: Invalid Bearer token format

Not all failures are about missing tokens. Sometimes the format is wrong:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_request"
Content-Type: application/json

{
  "error": "invalid_request",
  "error_description": "Authorization header must be in the format 'Bearer <token>'.",
  "error_code": "AUTH_INVALID_HEADER"
}

This is one of the best examples of authentication error handling in API responses because it tells the client exactly what to fix without exposing any sensitive detail.

Example: Expired token with guidance to refresh

Modern APIs are increasingly strict about short-lived tokens, especially in 2024–2025 as security guidance tightens. Here’s a pattern you’ll see in OAuth-based APIs:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="The access token expired"
Content-Type: application/json

{
  "error": "token_expired",
  "error_description": "Your access token has expired. Use the refresh token to obtain a new access token.",
  "error_code": "AUTH_TOKEN_EXPIRED",
  "action": "refresh_token",
  "docs_url": "https://api.example.com/docs/auth#refresh-tokens"
}

Notice the pattern:

  • The error is specific: token_expired, not just unauthorized.
  • The action field tells the client what to do next.
  • docs_url gives a stable reference for developers.

This kind of response is a strong example of error handling that both protects security and improves developer experience.

Example: 403 Forbidden vs 401 Unauthorized

One of the most common questions about examples of authentication error handling in API responses is when to use 401 vs 403.

A typical pattern:

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "forbidden",
  "error_description": "Authenticated user does not have access to this resource.",
  "error_code": "AUTH_FORBIDDEN",
  "required_scope": "payments:write"
}

Here the client is authenticated, but lacks permission. The required_scope field is gold for debugging OAuth scope issues. In 2024–2025, more APIs are adding this kind of hint because it reduces support tickets and speeds up onboarding.

Example: IP-based authentication restrictions

Some APIs combine token-based auth with IP allowlists. A realistic response looks like this:

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "ip_not_allowed",
  "error_description": "Requests from IP 203.0.113.42 are not allowed for this API key.",
  "error_code": "AUTH_IP_RESTRICTED",
  "support_id": "c1a9f6e2-7b4d-4a21-9a5d-8de1e3f2c9ab"
}

The support_id lets your support team trace logs without exposing internal identifiers. This is a practical example of how to balance security with debuggability.

Example: Rate-limited authentication attempts

Brute-force protection is a big topic in current security guidance. Many APIs now return rate-limit style errors even on authentication endpoints:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{
  "error": "too_many_attempts",
  "error_description": "Too many failed authentication attempts. Try again in 60 seconds.",
  "error_code": "AUTH_RATE_LIMITED",
  "retry_after": 60
}

This is one of the best examples of authentication error handling in API responses because it:

  • Uses 429, which is widely recognized.
  • Duplicates Retry-After in the JSON body for clients that don’t read headers.
  • Avoids saying whether the username exists, which aligns with security best practices.

Example: OAuth authorization error (invalid redirect URI)

Authentication isn’t just about tokens. OAuth flows introduce their own failure modes:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "invalid_redirect_uri",
  "error_description": "The redirect_uri does not match any registered URLs.",
  "error_code": "OAUTH_INVALID_REDIRECT_URI",
  "allowed_redirect_uris": [
    "https://app.example.com/oauth/callback"
  ]
}

This kind of response is an example of handling configuration errors in a way that’s both strict and helpful. Instead of a vague 400, the client sees exactly what’s wrong and how to fix it.

Example: API key revoked for security reasons

Revoked credentials are increasingly common as companies tighten security and comply with regulations. A realistic pattern:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "error": "api_key_revoked",
  "error_description": "This API key has been revoked. Create a new key in the dashboard.",
  "error_code": "AUTH_KEY_REVOKED",
  "dashboard_url": "https://dashboard.example.com/api-keys"
}

This is a clean example of an authentication error that:

  • Does not reveal why it was revoked (security/privacy).
  • Gives a clear next step (dashboard_url).
  • Uses a stable error_code so client libraries can show a tailored message.

Patterns behind the best examples of authentication error handling in API responses

Looking across these real examples of authentication error handling in API responses, some patterns show up again and again.

Consistent use of HTTP status codes

Good APIs treat HTTP status codes as a contract:

  • 401 Unauthorized when the client is not authenticated or the token is invalid.
  • 403 Forbidden when the client is authenticated but not allowed to access the resource.
  • 400 Bad Request when the authentication request itself is malformed (like an invalid OAuth redirect URI).
  • 429 Too Many Requests when rate limiting kicks in on login or token endpoints.

The best examples use these codes consistently so client libraries can behave predictably.

For reference, the IETF’s HTTP specification outlines these codes in detail: https://www.rfc-editor.org/rfc/rfc9110.

Stable, machine-readable error codes

Human-readable messages are for developers. Machine-readable codes are for your SDKs and integrations.

Across the examples of authentication error handling in API responses above, notice the repeated pattern:

{
  "error": "token_expired",
  "error_description": "Your access token has expired.",
  "error_code": "AUTH_TOKEN_EXPIRED"
}

You don’t have to use both error and error_code, but you should:

  • Pick one field for short, stable identifiers (token_expired).
  • Keep it consistent across endpoints.
  • Avoid changing codes once they’re public; add new ones instead.

This is the kind of detail that separates toy APIs from production-grade systems.

Security vs. usability: how much do you reveal?

Authentication errors live in a security minefield. You want to help legitimate developers while not giving attackers free information.

A few practical guidelines drawn from security recommendations such as NIST’s digital identity guidance (https://pages.nist.gov/800-63-3/):

  • Avoid saying whether a username or email exists during login. Use generic messages like “Invalid credentials” instead of “User not found.”
  • Be more specific after authentication, where appropriate. For example, telling an authenticated user that a token is expired is usually fine.
  • Do not echo back secrets (passwords, tokens, client secrets) in error responses. Ever.
  • Log detailed information on the server side, not in the response body.

The earlier rate-limit example of authentication error handling shows this: it mentions “Too many failed authentication attempts” without confirming the validity of any identifier.

Localization and developer experience

If your API is used globally, consider how error messages behave across languages:

  • Keep error_code language-agnostic.
  • Allow clients to map error_code to localized messages in their UI.
  • Keep error_description in English by default, or provide a way to request localized responses via headers (for example, Accept-Language).

This lets you maintain a single source of truth for error semantics while giving product teams freedom to present friendly, localized messages.

The context around authentication error handling is shifting fast. Some current trends that affect how you design responses:

Short-lived tokens and refresh flows

Driven by security guidance and high-profile breaches, many platforms are shortening access token lifetimes and leaning on refresh tokens. That means more token_expired scenarios and more pressure on clear error handling.

The best examples of authentication error handling in API responses now:

  • Distinguish between “expired” and “invalid for other reasons.”
  • Tell clients explicitly when to use a refresh token.
  • Include a docs_url or similar hint for developers.

Device-bound and phishing-resistant authentication

With growing adoption of WebAuthn and passkeys, some APIs are starting to include error details about device binding or authenticator state. While you won’t expose hardware details in raw API responses, you might see patterns like:

{
  "error": "webauthn_verification_failed",
  "error_description": "The provided assertion could not be verified.",
  "error_code": "AUTH_WEBAUTHN_FAILED"
}

This is still an example of standard authentication error handling; it just reflects newer authentication mechanisms.

Compliance and audit requirements

Regulatory pressure (GDPR in the EU, state-level privacy laws in the US, and sector-specific rules) is pushing teams to:

  • Avoid storing or echoing sensitive identifiers in responses.
  • Provide traceable support_id or request_id values instead.

You saw this in the IP restriction example, where the response included a support_id but no internal log keys.

Designing your own examples of authentication error handling in API responses

If you’re building or refactoring an API today, use these patterns as a checklist when designing your own responses.

Structure your error payloads

A practical baseline structure that reflects the best examples of authentication error handling in API responses:

{
  "error": "token_expired",
  "error_description": "Your access token has expired.",
  "error_code": "AUTH_TOKEN_EXPIRED",
  "request_id": "b3f6b9a2-8c4d-4c32-9b7f-2d5a9e1f4d10",
  "docs_url": "https://api.example.com/docs/errors#AUTH_TOKEN_EXPIRED"
}

Key design choices:

  • error: short identifier, used in logs and client logic.
  • error_description: clear, friendly explanation.
  • request_id: correlation ID for debugging.
  • docs_url: deep link to your documentation.

You can expand this with fields like action, required_scope, or retry_after when appropriate.

Keep behavior predictable across endpoints

Clients should be able to rely on consistent behavior:

  • Same error shape for all authentication-related errors.
  • Same HTTP status codes for the same class of problems.
  • Same error_code values irrespective of endpoint.

When engineers talk about the best examples of authentication error handling in API responses, they’re often praising this predictability more than any fancy JSON structure.

Document your errors like a contract

Good error handling is only as good as its documentation. Consider:

  • A dedicated “Error Codes” section in your API docs.
  • Grouping authentication errors separately from validation or server errors.
  • Providing at least one example of each error type, with sample request and response.

High-quality public docs from large providers (Stripe, GitHub, Google) are worth studying, even if you can’t link directly to them from your own site.

FAQ: examples of authentication error handling in API responses

Q: Can you give an example of a good 401 vs a bad 401 response?
A good 401 response might look like:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token"
Content-Type: application/json

{
  "error": "invalid_token",
  "error_description": "Access token is missing or invalid.",
  "error_code": "AUTH_INVALID_TOKEN"
}

A bad 401 is just 401 Unauthorized with an empty body, forcing developers to guess what went wrong.

Q: Should I ever return 200 OK with an authentication error in the body?
No. That pattern breaks HTTP semantics and makes client behavior unpredictable. Stick to the patterns you see in the best examples of authentication error handling in API responses: 401, 403, 400, or 429, with a clear JSON body.

Q: How detailed should my error descriptions be?
Detailed enough that a developer can fix the problem without opening a support ticket, but not so detailed that an attacker learns about your internal systems. The earlier examples include specific guidance like “Use the refresh token to obtain a new access token” without exposing stack traces or database details.

Q: Are there standard formats for API error responses?
There are emerging patterns, like the problem+json format from RFC 7807, but adoption is mixed. You can adapt those ideas while keeping the authentication-specific patterns you’ve seen in these examples of authentication error handling in API responses.

Q: How do I test my authentication error handling?
Automate it. Write integration tests that intentionally omit tokens, send expired tokens, use revoked keys, and trigger rate limits. Assert against both the HTTP status and the JSON body (error, error_code, and any action fields). The more your tests mirror the real examples you want in production, the fewer surprises your clients will face.

Explore More Error Handling in API Responses

Discover more examples and insights in this category.

View All Error Handling in API Responses