DEV Community

Cover image for A Practical Checklist for Debugging a Broken API Request (With Free Browser Tools)
Bellal Hossain
Bellal Hossain

Posted on

A Practical Checklist for Debugging a Broken API Request (With Free Browser Tools)

You send a request. You get a 401. Or a 400. Or an empty response and a vague error.

Most API bugs come from a small set of causes: a malformed token, bad encoding, wrong headers, or a network problem. The trick is checking them in a sensible order instead of guessing.

Here's the checklist I use, with code for each step. Some steps are quicker with a browser utility than with a throwaway script, so I've linked a few.

Security note: never paste production secrets, private keys, or live tokens into any online tool. Use test values, or decode locally. I'll show local alternatives throughout.

Step 1: Reproduce the request in its simplest form

Strip the request down to something you can copy, share, and re-run. curl is the universal format:

curl -i -X POST https://api.example.com/v1/orders \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"item":"book","qty":2}'
Enter fullscreen mode Exit fullscreen mode

The -i flag prints response headers, which you'll need later.

Once it works in curl, converting it to your language is mechanical. A cURL to Fetch converter does this, and the result looks like:

const res = await fetch('https://api.example.com/v1/orders', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ item: 'book', qty: 2 }),
});
Enter fullscreen mode Exit fullscreen mode

If curl works and your code doesn't, the bug is in your code, not the API. That one check saves hours.

Step 2: Read the status code properly

Don't just check res.ok. The exact code narrows the problem:

Code Usually means
400 Malformed body, bad parameters, invalid JSON
401 Missing, expired, or invalid credentials
403 Authenticated, but not allowed
404 Wrong path, or resource doesn't exist
405 Wrong HTTP method
415 Wrong or missing Content-Type
422 Valid JSON, but failed validation
429 Rate limited, so check Retry-After
5xx Server-side; retry with backoff, then report

If you hit an unfamiliar code, an HTTP status code checker gives a quick explanation.

And always log the response body on errors. Many APIs put the real reason there:

if (!res.ok) {
  const body = await res.text();
  console.error(res.status, body);
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Decode the token (JWTs)

If you get a 401 with a Bearer token, check the token before blaming the server. A JWT is three Base64URL-encoded parts separated by dots: header.payload.signature. You can decode the first two locally:

function decodeJwt(token) {
  const [h, p] = token.split('.');
  const dec = (s) =>
    JSON.parse(
      atob(s.replace(/-/g, '+').replace(/_/g, '/'))
    );
  return { header: dec(h), payload: dec(p) };
}

const { payload } = decodeJwt(token);
console.log(payload.exp, new Date(payload.exp * 1000));
Enter fullscreen mode Exit fullscreen mode

The usual suspects:

  • exp (expiry) has passed. Note that it's in seconds, hence the * 1000.
  • aud (audience) or iss (issuer) doesn't match what the API expects.
  • nbf (not before) is in the future, often from clock skew between machines.

A JWT decoder displays these fields readably. Decoding only reads the token. It doesn't verify the signature, so never trust decoded claims for authorization decisions.

Step 4: Check encoding

Encoding bugs are sneaky because the request looks right to the eye.

URL encoding. Query parameters with spaces, &, +, or non-ASCII characters must be encoded:

const q = 'coffee & tea';

// Wrong: breaks the query string
`/search?q=${q}`;

// Right
`/search?q=${encodeURIComponent(q)}`; // /search?q=coffee%20%26%20tea
Enter fullscreen mode Exit fullscreen mode

A tip: use URLSearchParams so you don't forget:

const params = new URLSearchParams({ q: 'coffee & tea', page: 2 });
fetch(`/search?${params}`);
Enter fullscreen mode Exit fullscreen mode

If a URL looks wrong, a URL parser splits it into protocol, host, path, and parameters so you can spot the problem. The URL encoder and decoder help with quick checks.

Base64. Used in Basic auth and many payloads:

// Basic auth header
const basic = btoa('user:password');
headers.Authorization = `Basic ${basic}`;

// Node.js
Buffer.from('user:password').toString('base64');
Enter fullscreen mode Exit fullscreen mode

Watch out for btoa failing on non-Latin characters. In that case, encode to UTF-8 bytes first. A Base64 decoder is handy for inspecting unknown strings.

Step 5: Validate your JSON

A single trailing comma or unquoted key gives you a 400 and an unhelpful message.

try {
  JSON.parse(body);
} catch (e) {
  console.error('Invalid JSON:', e.message);
}
Enter fullscreen mode Exit fullscreen mode

For a large payload, paste it into a JSON formatter or JSON validator to locate the error visually. If the API publishes a schema, a JSON Schema validator checks your payload against it and often explains why you got a 422.

Bonus: if you're working in TypeScript, generating types from a sample response, for example with a JSON to TypeScript converter, prevents a whole class of future bugs.

Step 6: Inspect the response headers

Headers hold answers that the body doesn't:

curl -I https://api.example.com/v1/orders
Enter fullscreen mode Exit fullscreen mode

Look for:

  • Content-Type: is it the format you expect?
  • Retry-After / X-RateLimit-*: rate limiting details.
  • WWW-Authenticate: tells you which auth scheme the server wants.
  • Location: redirect targets (a redirect can silently change POST to GET).
  • Access-Control-Allow-*: CORS problems, which only show up in browsers.

An HTTP headers checker fetches these without opening a terminal.

A quick word on CORS

If it works in curl but fails in the browser with a CORS error, the server isn't sending the right Access-Control-Allow-Origin header. Preflight (OPTIONS) requests are triggered by custom headers like Authorization, so make sure the server handles them. You can't fix CORS from client code.

Step 7: Rule out network problems

If you get timeouts or "host not found", step back from the application layer:

# Does the domain resolve?
dig api.example.com +short

# Can you reach it?
curl -v --max-time 10 https://api.example.com/health

# Where does the path break?
traceroute api.example.com
Enter fullscreen mode Exit fullscreen mode

A DNS lookup tool shows a domain's records, which is useful right after a DNS change when you suspect propagation. An SSL checker catches expired or misconfigured certificates, a common cause of sudden failures.

Step 8: Time-related gotchas

Two more causes of "it worked yesterday":

  • Expiring credentials. Tokens, signed URLs, and API keys with expiry. Compare exp against Date.now() / 1000.
  • Scheduled jobs that didn't run. If a cron-based job isn't triggering, verify the expression. A cron expression generator translates */15 9-17 * * 1-5 into plain language so you can confirm it means what you think.

If you're handling timestamps, a Unix timestamp converter helps you check values quickly, and remember the seconds vs milliseconds trap.

The checklist (copy this)

  1. Reproduce with curl. Does it work outside your code?
  2. Read the exact status code and the response body.
  3. Decode the token. Check exp, aud, iss, nbf.
  4. Check URL and Base64 encoding.
  5. Validate the JSON body (and schema, if one exists).
  6. Inspect response headers (Content-Type, Retry-After, Location, CORS).
  7. Rule out DNS, TLS, and connectivity.
  8. Check time-related causes: expiry, clock skew, cron schedules.

Tools I keep bookmarked

For one-off checks, I'd rather paste a test value into a browser utility than write a script I'll throw away. Noloii's Developer & Tech Tools collects many of these in one free place: encoders and decoders, formatters, validators, network utilities, and converters, with no install. Just remember the security note at the top. Use test data, and for anything sensitive, decode locally with the snippets above.

What's the strangest API bug you've tracked down? Tell me in the comments. I'm collecting them.

Top comments (0)