How to debug REST API errors: CORS, 401, 403, 404, 415 and timeouts

By Sayaib SarkarUpdated 6 min read

On this page 9 sections

Short answer: reproduce the failing request outside your app, read the status code and response body before anything else, then change one thing at a time. Most API bugs fall into a handful of patterns, and the status code tells you which one. If the browser says "blocked by CORS policy" but the same request works in an API client, the problem is the server's CORS headers, not your request.

This guide goes through the errors developers hit most often when integrating an API, what each one actually means, and how to confirm the cause in a few minutes from VS Code.

Step 1: reproduce the request outside your app

Your app adds layers: an HTTP library, interceptors, a proxy, the browser. Take them away first.

  1. In the browser, open DevTools → Network, right-click the failing request and choose Copy → Copy as cURL.
  2. In VS Code, open the REST API client in DevSnip Pro and paste the cURL command into the address bar. The method, headers and body are imported.
  3. Send it. Now you can change one header at a time and see the effect immediately.

If the request works here but fails in the browser, jump to the CORS section. If it fails in both, the status code below tells you where to look.

Read the status code and the body

Before changing code, read what the server sent back. Many APIs explain the problem in the response body, for example {"error": "invalid_token", "error_description": "The access token expired"}.

StatusWhat it usually meansCheck first
400 Bad RequestThe body or parameters are invalidThe response body; required fields; JSON syntax
401 UnauthorizedNo valid credentials were sentThe Authorization header; token expiry
403 ForbiddenCredentials are valid but not allowed to do thisToken scopes or roles; API key permissions
404 Not FoundWrong URL, or the resource doesn't existBase URL, path, trailing slash, API version
405 Method Not AllowedRight URL, wrong methodGET vs POST; the Allow response header
415 Unsupported Media TypeThe server can't read the body formatThe Content-Type header
422 Unprocessable EntityValid JSON, but it failed validationThe response body's list of field errors
429 Too Many RequestsRate limitedThe Retry-After header
5xxThe server failedServer logs; whether a different input works

401 Unauthorized: the credentials didn't arrive or aren't valid

  • Is the header there at all? Some HTTP clients drop Authorization when following a redirect to another host. Check the request that was actually sent, not the code that builds it.
  • Is the format right? Most APIs expect Authorization: Bearer <token> with exactly one space, and the word Bearer once. A doubled Bearer Bearer … is a common bug when the token is stored with its prefix.
  • Has the token expired? Paste a JWT into DevSnip Pro's JWT Decoder & Signer: it shows the header and claims and checks exp. Also compare aud (audience) and iss (issuer) with what the API expects. The decoder runs locally, so a real token never goes to a website.

403 Forbidden: who you are is fine, what you're doing isn't

The server knows who you are, but your token or key lacks permission for this operation. Look at the scopes or roles in the token's claims, the permissions of the API key, and whether the resource belongs to a different account or tenant. Retrying the same request with a fresh token won't help unless the new token has more permissions.

Some servers also return 403 when a firewall or WAF blocks the request (for example a missing User-Agent). The response body often looks like an HTML error page rather than JSON in that case.

404 Not Found and 405 Method Not Allowed

Compare the URL character by character with the API's documentation:

  • The base URL and API version (/v1/ vs /v2/).
  • A trailing slash: /users and /users/ are different routes on many servers.
  • Path parameters that were never substituted, such as /users/undefined or /users/{id}.

Using environment variables such as {{baseUrl}} for the host, instead of hard-coding it in every request, removes a whole class of these mistakes. If the path is right and you get 405, the route exists but not for that method; the Allow response header lists the ones it accepts.

415 and 400: the body isn't what the server expects

A JSON body needs Content-Type: application/json. Without it, many frameworks don't parse the body at all, and your handler sees it as empty. That shows up as a 415, or as a 400 complaining that required fields are missing even though you sent them.

HTTP
POST /v1/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <token>

{"name": "Ada", "role": "admin"}

If the server wants form data instead, send application/x-www-form-urlencoded (OAuth token endpoints usually do). To check a JSON body against the API's schema before sending it, use DevSnip Pro's JSON Schema Validator: it lists every violation with its path.

CORS: "blocked by CORS policy"

CORS is enforced only by browsers. curl, Postman, mobile apps and server-to-server calls ignore it, which is why a request can work in an API client and fail in your web app. The fix is always on the server: it must send the right Access-Control-* headers for your page's origin.

The common causes:

  • No Access-Control-Allow-Origin header, or one with a different origin. http://localhost:3000 and http://localhost:5173 are different origins.
  • The preflight OPTIONS request fails. Requests with a JSON body or an Authorization header trigger a preflight. If the server answers it with 401, 404 or 405 (often because auth middleware runs before CORS), the browser blocks the real request.
  • Credentials with a wildcard. With cookies (credentials: 'include'), the server must return the exact origin and Access-Control-Allow-Credentials: true. Access-Control-Allow-Origin: * is not allowed.
  • Errors without CORS headers. If the server returns a 500 without CORS headers, the browser reports a CORS error and hides the real one. Return the CORS headers on error responses too.

DevSnip Pro's CORS Builder & Debugger has three modes for this: paste the console error to get the likely cause and fix, paste the response headers to check them the way a browser would, or generate a correct configuration for Express, NestJS, Next.js, Fastify, nginx or FastAPI.

Timeouts and requests that never finish

  • Check that something is listening at all. DevSnip Pro's Port & Network Toolkit shows which local ports are free: if the port your API should use is free, the server isn't running there.
  • A request to localhost from a phone or emulator goes to the device itself, not your computer. Use your machine's LAN address, or the emulator's alias for the host.
  • Large responses and slow endpoints may need a longer timeout. In DevSnip Pro, devsnip.apiTimeout sets the default (30 seconds), a per-request timeout overrides it, and Cancel stops a request early.

Then fix it in code

Once a request works in the API client, generate the same call as JavaScript (fetch or Axios), Python, Go, Java or C# and compare it with your code. The difference is usually a header, the body encoding or the URL.

Try it in your own editor

Free, no account, runs locally. Requires VS Code 1.93+ on Windows, macOS or Linux.

code --install-extension sayaib.hue-console