API Error Reference
By the Syncfusion DevTutorials team · Last updated
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 |