API HTTP Status Codes: The Complete Developer Cheat Sheet (2026)
Every API developer needs to understand HTTP status codes. They're how APIs communicate success, failure, rate limits, and outages. This is your comprehensive, bookmark-worthy reference.
Quick Reference Table
| Code | Name | Meaning | Action |
|---|---|---|---|
| 1xx β Informational | |||
| 100 | Continue | Request received, continue | Proceed with request body |
| 101 | Switching Protocols | Upgrading to WebSocket | Connection upgraded |
| 2xx β Success | |||
| 200 | OK | Request succeeded | Process response |
| 201 | Created | Resource created | Check Location header |
| 202 | Accepted | Processing async | Poll for completion |
| 204 | No Content | Success, no body | No response to parse |
| 3xx β Redirection | |||
| 301 | Moved Permanently | Resource relocated | Update bookmark |
| 302 | Found | Temporary redirect | Follow redirect |
| 304 | Not Modified | Cached version OK | Use cached data |
| 4xx β Client Error | |||
| 400 | Bad Request | Invalid syntax | Fix request format |
| 401 | Unauthorized | Auth required | Provide credentials |
| 403 | Forbidden | Access denied | Check permissions |
| 404 | Not Found | Resource doesn't exist | Verify endpoint/ID |
| 405 | Method Not Allowed | Wrong HTTP method | Use GET/POST/etc correctly |
| 409 | Conflict | Resource state conflict | Resolve conflict |
| 422 | Unprocessable Entity | Validation failed | Fix input data |
| 429 | Too Many Requests | Rate limit exceeded | Back off and retry |
| 5xx β Server Error | |||
| 500 | Internal Server Error | API crashed | Retry with backoff |
| 502 | Bad Gateway | Upstream failed | API may be down |
| 503 | Service Unavailable | API overloaded | API is down/deploying |
| 504 | Gateway Timeout | Upstream timeout | API is slow/down |
1xx: Informational β "Hold On, We're Working on It"
These are rare in REST APIs. You'll see them with WebSockets or long-running uploads.
100 Continue
What it means: "I got your headers, send me the rest of the request body."
When you'll see it: Large file uploads where the client checks if the server will accept the file before sending all the data.
How to handle:
// Browsers handle this automatically
// Just send your request normally
101 Switching Protocols
What it means: "We're upgrading this connection to WebSocket."
When you'll see it: WebSocket handshake.
Example:
const ws = new WebSocket('wss://api.example.com/stream');
ws.onopen = () => console.log('Connection upgraded to WebSocket');
2xx: Success β "Everything Worked!"
200 OK
What it means: The request succeeded. This is the "happy path."
When APIs return it:
- GET requests that return data
- POST requests that process data
- PUT/PATCH requests that update resources
Real example (GitHub API):
// GET /user
fetch('https://api.github.com/user', {
headers: { 'Authorization': 'token YOUR_TOKEN' }
})
.then(res => res.json()) // 200 OK + user data
201 Created
What it means: "Resource successfully created."
When APIs return it:
- POST requests that create new resources
- Response includes
Locationheader pointing to the new resource
Real example (Stripe API):
// POST /v1/customers
// Response: 201 Created
// Location: /v1/customers/cus_123456789
{
"id": "cus_123456789",
"object": "customer",
"email": "user@example.com"
}
How to handle:
if (response.status === 201) {
const location = response.headers.get('Location');
console.log('Created resource at:', location);
}
202 Accepted
What it means: "Request accepted, but processing asynchronously."
When APIs return it:
- Long-running operations (video encoding, bulk imports)
- Background jobs
- Webhooks that trigger later
Real example (AWS S3):
// POST /restore (S3 Glacier restore)
// Response: 202 Accepted
// Poll /status until 200 OK
How to handle:
async function pollUntilComplete(statusUrl) {
while (true) {
const res = await fetch(statusUrl);
if (res.status === 200) return res.json();
if (res.status === 202) {
await sleep(5000); // Wait 5 seconds
continue;
}
throw new Error('Job failed');
}
}
204 No Content
What it means: "Success, but no response body."
When APIs return it:
- DELETE requests (resource deleted, nothing to return)
- PUT/PATCH updates where the response would just echo the request
Real example (Stripe API):
// DELETE /v1/customers/cus_123
// Response: 204 No Content (no JSON body)
How to handle:
if (response.status === 204) {
console.log('Success, but no content to parse');
// Don't try to call response.json()
}
3xx: Redirection β "Look Over There Instead"
301 Moved Permanently
What it means: "This endpoint has moved forever. Update your bookmarks."
When APIs return it:
- API versioning (old endpoint β new endpoint)
- Domain changes
How to handle:
// Most HTTP clients auto-follow redirects
// But update your code to use the new URL
304 Not Modified
What it means: "Your cached version is still good."
When APIs return it:
- GET requests with
If-None-MatchorIf-Modified-Sinceheaders
How to handle:
const etag = localStorage.getItem('user-etag');
const res = await fetch('/api/user', {
headers: { 'If-None-Match': etag }
});
if (res.status === 304) {
console.log('Use cached data');
return JSON.parse(localStorage.getItem('user-data'));
}
4xx: Client Errors β "You Messed Up"
400 Bad Request
What it means: "Your request is malformed or missing required fields."
When APIs return it:
- Invalid JSON syntax
- Missing required parameters
- Wrong data types
Real example (OpenAI API):
// POST /v1/chat/completions
// Missing "model" field
{
"error": {
"message": "you must provide a model parameter",
"type": "invalid_request_error"
}
}
How to handle:
if (response.status === 400) {
const error = await response.json();
console.error('Bad request:', error.message);
// Fix the request and retry
}
401 Unauthorized
What it means: "You need to authenticate first."
When APIs return it:
- Missing API key/token
- Expired token
- Invalid credentials
Real example (GitHub API):
// GET /user without Authorization header
// Response: 401 Unauthorized
{
"message": "Requires authentication"
}
How to handle:
if (response.status === 401) {
// Redirect to login or refresh token
refreshAuthToken();
}
403 Forbidden
What it means: "I know who you are, but you don't have permission."
When APIs return it:
- Authenticated but not authorized
- API key lacks required scope/permission
- Resource access denied
Real example (Stripe API):
// Trying to access another account's customer
{
"error": {
"type": "invalid_request_error",
"message": "No such customer: cus_999"
}
}
How to handle:
if (response.status === 403) {
console.error('Access denied. Check API key permissions.');
// Don't retry β you need higher permissions
}
404 Not Found
What it means: "This endpoint or resource doesn't exist."
When APIs return it:
- Typo in URL
- Resource was deleted
- Wrong API version
Real example (GitHub API):
// GET /repos/nonexistent/repo
// Response: 404 Not Found
{
"message": "Not Found"
}
How to handle:
if (response.status === 404) {
console.log('Resource not found');
// Show 'Not found' UI, don't retry
}
405 Method Not Allowed
What it means: "You used GET instead of POST (or vice versa)."
When APIs return it:
- POST to read-only endpoint
- GET to create endpoint
How to handle:
// Check API docs and use correct HTTP method
// GET for reading, POST for creating, PUT/PATCH for updating, DELETE for deleting
409 Conflict
What it means: "This conflicts with the current state of the resource."
When APIs return it:
- Trying to create a resource that already exists
- Version mismatch (optimistic locking)
- Concurrent updates
Real example (Stripe API):
// Creating duplicate customer with same email
{
"error": {
"type": "invalid_request_error",
"message": "Customer already exists"
}
}
How to handle:
if (response.status === 409) {
// Fetch current state, merge changes, retry
const current = await fetchCurrentState();
const merged = mergeChanges(current, yourChanges);
await retry(merged);
}
422 Unprocessable Entity
What it means: "Request syntax is valid, but data validation failed."
When APIs return it:
- Email format invalid
- Password too short
- Business logic validation failed
Real example (GitHub API):
// Creating repo with invalid name
{
"message": "Validation Failed",
"errors": [
{
"field": "name",
"code": "invalid"
}
]
}
How to handle:
if (response.status === 422) {
const errors = await response.json();
errors.errors.forEach(err => {
console.error(`${err.field}: ${err.message}`);
});
// Show validation errors to user
}
429 Too Many Requests
What it means: "Slow down! You hit the rate limit."
When APIs return it:
- Exceeded requests per second/minute/hour
- Burst limit exceeded
Real example (OpenAI API):
{
"error": {
"message": "Rate limit exceeded. Please retry after 20 seconds.",
"type": "rate_limit_error"
}
}
How to handle:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const res = await fetch(url, options);
if (res.status === 429) {
const retryAfter = res.headers.get('Retry-After') || 60;
console.log(`Rate limited. Retrying in ${retryAfter}s...`);
await sleep(retryAfter * 1000);
continue;
}
return res;
}
throw new Error('Max retries exceeded');
}
5xx: Server Errors β "We Messed Up (or We're Down)"
These are the codes you'll see during API outages.
500 Internal Server Error
What it means: "Something crashed on our end."
When you'll see it:
- Unhandled exception in API code
- Database connection failed
- Null pointer exception
Real example (any API during outage):
{
"error": "Internal server error",
"request_id": "req_abc123"
}
How to handle:
if (response.status === 500) {
// Retry with exponential backoff
await retryWithBackoff(request);
}
502 Bad Gateway
What it means: "The API's upstream dependency failed."
When you'll see it:
- Load balancer can't reach backend
- Reverse proxy got invalid response
- Common during deployments
How to handle:
if (response.status === 502) {
console.log('API may be deploying. Retry in 30s...');
await sleep(30000);
return retry(request);
}
503 Service Unavailable
What it means: "API is temporarily down (maintenance, overload, or outage)."
When you'll see it:
- Scheduled maintenance
- Traffic spike overwhelming servers
- Major outages
Real example (AWS during outage):
<Error>
<Code>ServiceUnavailable</Code>
<Message>Reduce your request rate.</Message>
</Error>
How to handle:
if (response.status === 503) {
const retryAfter = response.headers.get('Retry-After');
if (retryAfter) {
console.log(`Service down. Retry after ${retryAfter}s`);
await sleep(retryAfter * 1000);
} else {
// Exponential backoff: 1s, 2s, 4s, 8s...
await sleep(Math.pow(2, attempt) * 1000);
}
return retry(request);
}
504 Gateway Timeout
What it means: "The API didn't respond in time (slow or down)."
When you'll see it:
- Database queries timing out
- Upstream API calls timing out
- Partial outages (some requests work, some timeout)
How to handle:
if (response.status === 504) {
console.log('API timed out. Possible performance issue.');
// Retry with longer timeout
return retry(request, { timeout: 60000 });
}
Status Codes Cheat Sheet (Printable)
2xx: Success
- 200 OK β Standard success
- 201 Created β Resource created
- 202 Accepted β Processing async
- 204 No Content β Success, no body
4xx: Client Errors
- 400 Bad Request β Fix request format
- 401 Unauthorized β Need auth
- 403 Forbidden β Permission denied
- 404 Not Found β Doesn't exist
- 429 Too Many Requests β Rate limited
5xx: Server Errors (Outages)
- 500 Internal Server Error β Retry
- 502 Bad Gateway β API may be down
- 503 Service Unavailable β API is down
- 504 Gateway Timeout β API slow/down
Retry Logic Best Practices
Use exponential backoff for 5xx errors:
async function retryWithBackoff(fn, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (error.status >= 500 && attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s, 8s, 16s
console.log(`Retry ${attempt + 1}/${maxRetries} in ${delay}ms...`);
await sleep(delay);
continue;
}
throw error;
}
}
}
Monitor API Status Codes Automatically
Don't wait for users to report errors. Monitor API status codes in real-time:
API Status Check tracks HTTP status codes for 120+ APIs and alerts you instantly when they start returning 5xx errors.
What we monitor:
- Error rate trends (spike in 500/502/503)
- Response time degradation (504 timeouts)
- Authentication failures (401/403 spikes)
- Rate limiting (429 patterns)
Start monitoring API status codes β
FAQ
What's the difference between 401 and 403?
- 401 Unauthorized: You're not authenticated (no credentials or invalid token)
- 403 Forbidden: You're authenticated, but don't have permission
Should I retry 4xx errors?
No. 4xx errors mean you made a mistake. Fix your request instead of retrying.
Exception: 429 (rate limit) β retry after the Retry-After period.
Should I retry 5xx errors?
Yes. 5xx errors are server-side issues. Retry with exponential backoff.
What's the difference between 502, 503, and 504?
- 502 Bad Gateway: Reverse proxy got invalid response from backend
- 503 Service Unavailable: API is down (maintenance/overload)
- 504 Gateway Timeout: Backend didn't respond in time
All three usually mean "API is having problems."
How do I know if an API is down?
5xx status codes (especially 502, 503, 504) indicate outages or severe performance issues. Monitor for patterns:
- Sustained 503 responses = full outage
- Intermittent 502/504 = partial outage or performance degradation
What does "Retry-After" header mean?
The Retry-After header (usually with 429 or 503) tells you when to retry. Wait that many seconds before retrying.
Why do some APIs return 200 with error in body?
Some APIs (especially older SOAP APIs) always return 200 and put error details in the response body. This is bad practice. Modern REST APIs use proper status codes.
Conclusion
HTTP status codes are how APIs communicate. Understand them, handle them correctly, and your apps will be more resilient.
Remember:
- 2xx = success
- 4xx = you messed up (don't retry)
- 5xx = API messed up (retry with backoff)
Bookmark this page for quick reference when debugging API integrations.
Related resources:
Top comments (0)