# API Error Reference

Course: [API](https://www.devtutorials.ai/learn/building-apis) · Section: Reference
Source: https://www.devtutorials.ai/learn/building-apis/reference/error-index

> Real error messages you will meet while building APIs, what causes each one, how to fix it and which lesson explains it.

The **API Error Reference** helps you understand common errors that occur when building or using APIs. For each error, this guide explains:

- What the error means
- What usually causes it
- How to fix it
- Which lesson explains the topic in more detail

If you see an error message, look for a keyword from that message, such as `CORS`, `401`, `IDX10223`, or `Cannot query field`, and find it in the relevant section below.

Read the entire error message before trying to fix it. The status code often tells you which area the problem belongs to: `4xx` usually means there is a problem with the request and `5xx` usually means there is a problem on the server. If the API returns Problem Details, the `title`, `detail`, and `errors` fields often point directly to the field or rule that caused the problem.

## HTTP Requests and Status Codes

| Message you see | Cause | Fix | Lesson |
|---|---|---|---|
| `405 Method Not Allowed` | The path exists, but not with this method (for example `DELETE /menu`) | Read the `Allow` header, and use one of those methods | HTTP Methods |
| `415 Unsupported Media Type` | A request with a body has no `Content-Type`, or the wrong one | Add `Content-Type: application/json` | HTTP Basics |
| `404 Not Found` on a URL that you are sure exists | A typo, a missing `/v1`, or the wrong id type (`/orders/abc` when the route expects `{id:int}`) | Compare the URL with the OpenAPI document | REST API Design |
| `404 Not Found` on your own order list, but not on a colleague's | The record belongs to another user, and the API hides it | Use a token for the owner | Authorization |
| `409 Conflict` "Too late to cancel" | The order has moved past the step where this change is allowed | Read the current status first, and offer only valid actions | Building a CRUD API |
| `409 Conflict` on a retried payment | The same Idempotency-Key was sent with a different body | Make a new key for a new request. Reuse a key only for retries | HTTP Methods |
| The server ignores your filter | There is a typo in the query string, such as `?maxprize=150` | Check the parameter name in the docs | HTTP Basics |
| The app shows "Success!" for a failed order | The API returns `200` with an error in the body | Return the right `4xx` with Problem Details | Status Codes |

## JSON and Validation

| Message you see | Cause | Fix | Lesson |
|---|---|---|---|
| `System.Text.Json.JsonException:` `'x' is an invalid start of` `a value. Path: $ \|` `LineNumber: 0` | The body is not valid JSON. For example, a quote is missing, there is a comma at the end, or the body is plain text | Fix the JSON. Paste it into a JSON checker | HTTP Basics |
| `JsonException: The JSON` `value could not be` `converted to System.Int32.` `Path: $.quantity` | A field has the wrong type, such as `"quantity": "two"` | Send a number: `"quantity": 2` | Request Validation |
| `BadHttpRequestException:` `Failed to read parameter` `"CreateOrder input" from` `the request body as JSON` | ASP.NET Core could not bind the body to your DTO (the class that holds the request data) | Check `Content-Type` and the JSON shape. ASP.NET Core answers `400` | Request Validation |
| `SyntaxError: Unexpected` `token } in JSON at` `position 41` | `await c.req.json()` in Hono got broken JSON | Catch the error, and return `400` Problem Details | HTTP Basics |
| `ZodError: [ { "code":` `"too_small", "minimum": 1,` `"path": ["quantity"],` `"message": "Number must be` `greater than or equal to` `1" } ]` | `schema.parse()` threw an error because a value broke a rule | Use `safeParse`, and return `422` with `error.flatten().fieldErrors` | Request Validation |
| `ZodError ... "code":` `"invalid_type",` `"expected": "number",` `"received": "string"` | A field has the wrong type | Send the right type, or use `z.coerce.number()` for query strings | Request Validation |
| `422` "Your order has some problems" with an `errors` object | Field rules failed (quantity, item, name) | Fix every field that is listed in `errors` | Request Validation |
| Customers set their own price | The request DTO has a `price` field (this is called over-posting) | Remove it from the request DTO. Look up the price on the server | Request Validation |

An unhandled `JsonException` or `ZodError` becomes a `500` with a stack trace. Catch it, and turn it into a `400` or `422` with a plain message.

## Authentication and Authorization

| Message you see | Cause | Fix | Lesson |
|---|---|---|---|
| `401 Unauthorized` with `WWW-Authenticate: Bearer` | No token or API key was sent | Add `Authorization: Bearer ...` or `X-Api-Key: ...` | Authentication |
| `401` with `WWW-Authenticate: Bearer` `error="invalid_token",` `error_description="The` `token expired at ..."` | The `exp` time of the JWT has passed | Get a new token (use the refresh token), and then retry once | Authentication |
| `IDX10223: Lifetime` `validation failed. The` `token is expired. ValidTo` `(UTC): ..., Current time` `(UTC): ...` | The same problem as above, seen in the ASP.NET Core server log | The client must refresh the token. If all tokens fail, check the server clock | Authentication |
| `IDX10214: Audience` `validation failed` | The token was made for another app (`aud` does not match) | Ask for a token for the audience of this API | Authentication |
| `IDX10205: Issuer` `validation failed` | The token came from a different issuer (`iss`) | Use the right identity provider, or fix `ValidIssuer` | Authentication |
| `IDX10503: Signature` `validation failed` | The token was changed, or it was signed with another key | Never edit tokens. Check the signing key | Authentication |
| `JWTExpired: "exp" claim` `timestamp check failed` (jose, in Hono) | The token is expired | Refresh the token | Authentication |
| `JWSSignatureVerificationFailed:` `signature verification` `failed` | The token was changed, or it uses another key | Get a fresh token. Check the secret or the key set | Authentication |
| `403 Forbidden` "Only staff can change the menu" | The caller is known, but does not have the role | Use a staff account. Do not loosen the check | Authorization |
| `403 Forbidden` "Missing scope orders:write" | The partner key or token does not have the scope | Ask for the scope when the token is made | Authorization |

## Browser and Network

| Message you see | Cause | Fix | Lesson |
|---|---|---|---|
| `Access to fetch at` `'https://api.brewbytes.dev/menu'` `from origin` `'https://brewbytes.app'` `has been blocked by CORS` `policy: No` `'Access-Control-Allow-Origin'` `header is present on the` `requested resource.` | The API did not say that this site may read its replies. The browser blocks the reply, not the server | Allow the exact origin on the server (`app.UseCors(...)` in C#, `cors()` middleware in Hono). Never use `*` with credentials | API Authorization and Security |
| `... blocked by CORS` `policy: Response to` `preflight request doesn't` `pass access control check` | The browser sent `OPTIONS` first (because of `Authorization` or JSON), and that request failed | Answer `OPTIONS` with `204` and the `Access-Control-Allow-Methods` and `-Headers` that you need. Do not require auth on `OPTIONS` | API Authorization and Security |
| `TypeError: Failed to fetch` | The request never got a reply. The cause can be a wrong host or port, a server that is not running, `http` vs `https`, or a CORS block | Open the URL in the browser. Check that the server is up. Read the console for a CORS line | API Authorization and Security |
| `net::ERR_CONNECTION_REFUSED` | Nothing is listening on that port | Start the server, or fix the port | HTTP Basics |
| The app works in curl but not in the browser | CORS only applies in browsers | Add the CORS headers on the server | API Authorization and Security |

CORS is a browser rule. curl, Postman and server-to-server calls ignore it. If only the browser fails, suspect CORS first.

## Limits, Gateways and Resilience

| Message you see | Cause | Fix | Lesson |
|---|---|---|---|
| `429 Too Many Requests` with `Retry-After: 30` | The caller went over its rate limit | Wait as long as `Retry-After` says. Cache replies. Stop polling every second | Rate Limits and Resilience |
| `429` again and again in a loop | The client retries at once and ignores `Retry-After` | Respect `Retry-After`. Use backoff with jitter (wait longer each time, with a small random change) | Rate Limits and Resilience |
| `502 Bad Gateway` | The gateway reached the service, but the reply was broken, or the service crashed | Check the service logs with the correlation id. Check the target of the gateway route | API Gateway |
| `503 Service Unavailable` | The service is overloaded, it is starting up, or a circuit breaker is open | Retry later with backoff. Check the readiness check | Rate Limits and Resilience |
| `504 Gateway Timeout` | The service behind the gateway was too slow | Find the slow call (look at p95 and traces). Add a timeout that is shorter than the one in the gateway | API Gateway |
| `TaskCanceledException: The` `request was canceled due` `to the configured` `HttpClient.Timeout of 100` `seconds elapsing` | An outgoing `HttpClient` call took too long | Set a short timeout, and retry idempotent calls with backoff | Rate Limits and Resilience |
| `BrokenCircuitException:` `The circuit is now open` `and is not allowing calls` | The circuit breaker in Polly stopped the calls after many failures | Return a fallback or `503`. The circuit will test again later | Rate Limits and Resilience |
| A customer is charged twice | A POST was retried without an Idempotency-Key | Send a key, and make the server check it | HTTP Methods |
| `/api/ordersecret` reaches the order service | The gateway route uses a plain `StartsWith` | Match the exact prefix, or the prefix plus `/` | API Gateway |

## Caching and Performance

| Message or symptom | Cause | Fix | Lesson |
|---|---|---|---|
| `304` never happens | The ETag is compared without quotes, or it is never sent | Send `ETag: "menu-1"`, and compare the full string | Performance and Caching |
| Users see an old price after an update | The ETag did not change | Build the ETag from a version number that goes up on every change | Performance and Caching |
| One user sees another user's data | A personal reply was marked `public` | Use `Cache-Control: private,` `no-store` | Performance and Caching |
| The list page gets slower as orders grow | N+1: one query for each order | Load all items in one query (`.Include` or `IN (...)`) | Performance and Caching |

## Versioning and Contracts

| Message or symptom | Cause | Fix | Lesson |
|---|---|---|---|
| `400` "Unknown API version" | The client sent `X-Api-Version: 9` | Send a supported version | API Versioning |
| A partner's app shows blank fields after a release | A field was renamed in place | Keep the old name in v1. Rename it in v2 | API Versioning |
| Requests suddenly fail with `400` | There is a new required request field | Make new fields optional | API Versioning |
| Generated client methods are called `Get2` | OpenAPI has no summaries or operation ids | Add `.WithSummary(...)` or `operationId` | OpenAPI |

## GraphQL

GraphQL usually answers `200` even when something fails. Always read the `errors` array.

| Message you see | Cause | Fix | Lesson |
|---|---|---|---|
| `Cannot query field "price"` `on type "Order".` | The field is not in the schema, or it is spelled wrong | Fix the spelling, or add the field and a resolver | GraphQL |
| `Field "order" argument` `"id" of type "Int!" is` `required, but it was not` `provided.` | A required argument is missing | Pass the argument, often through a variable | GraphQL |
| `Variable "$id" of type` `"String" used in position` `expecting type "Int!".` | The type of the variable does not match the schema | Declare `$id: Int!` | GraphQL |
| `Cannot return null for` `non-nullable field` `Order.customer.` | A resolver returned `null` for a `!` field | Fix the resolver, or make the field nullable | GraphQL |
| `Query is too deep` / `Query cost exceeds limit` | A very deep or large query hit the limit of the server | Ask for less, or split the query | GraphQL |
| The app shows an empty screen, with no error | The app only checks the status code | Read `errors` in every reply | GraphQL |
| A slow list query, and a busy database | N+1 resolvers | Batch the calls with a DataLoader | GraphQL |

## gRPC

gRPC has its own status codes. They are sent in the `grpc-status` trailer.

| Message you see | Cause | Fix | Lesson |
|---|---|---|---|
| `Status(StatusCode="InvalidArgument",` `Detail="amount must be` `positive")` / `3 INVALID_ARGUMENT: amount` `must be positive` | The request message broke a rule (like `400`) | Fix the field that is named in the detail | gRPC |
| `Status(StatusCode="Unavailable",` `Detail="Error connecting` `to subchannel.")` / `14 UNAVAILABLE: No` `connection established` | The server is down, the address is wrong, or TLS does not match | Check the address and the port. Retry with backoff | gRPC |
| `4 DEADLINE_EXCEEDED:` `Deadline exceeded` | The call took longer than its deadline | Find the slow step. Set a sensible deadline | gRPC |
| `5 NOT_FOUND` | The requested record does not exist (like `404`) | Check the id | gRPC |
| `16 UNAUTHENTICATED` / `7 PERMISSION_DENIED` | No valid credentials / not allowed (like `401` / `403`) | Send metadata with the token. Check the role | gRPC |
| `12 UNIMPLEMENTED: Method` `not found` | The client and the server use different `.proto` files or package names | Share one `.proto` file, and rebuild both sides | gRPC |
| Wrong values after a deploy, with no error | A field number was reused or changed in the `.proto` file | Never reuse numbers. Mark old ones `reserved` | gRPC |
| A field reads as `0` or `""` when it was never sent | The defaults in proto3 do not mean "missing" | Use `optional` when you must tell "not set" from zero | gRPC |
| The browser cannot call the gRPC service | Browsers cannot speak raw gRPC over HTTP/2 | Use gRPC-Web through a proxy, or offer REST for browsers | gRPC |
