Debug a Failing API Request

Intermediate 20 min 5 steps

The problem

Your API call is failing with an unclear error — a 400 Bad Request, a 401 Unauthorized, a mysterious 422, or an intermittent 429. It could be a malformed body, an expired token, a wrong header, or a rate limit. This workflow systematically eliminates each possible cause in the right order, from the outermost layer (the request format) inward to authentication and then server-side constraints.

What you'll accomplish

The failing request converted to readable code with headers and body visible
Request body validated and any JSON syntax or structure issues fixed
JWT claims decoded — expiry, issuer, audience, and algorithm verified
Rate limit headers parsed and reset time identified for 429 failures
The working endpoint documented in Markdown or OpenAPI for your team

Step-by-step

1

Convert the failing cURL command to readable code

Paste the failing cURL command into the Curl to Code Converter and select your language (Python, JavaScript/Node, Go, Ruby, PHP). Viewing the request as code immediately surfaces issues that are invisible in a raw cURL string: URL encoding problems in query parameters that look correct in the terminal but fail when parsed, headers split across multiple lines that combine incorrectly, JSON request bodies where escaped quotes mask syntax errors, and base64-encoded credentials in Authorization headers that look valid but were generated incorrectly. The code view also shows the exact headers being sent, which matters when debugging authentication — Bearer tokens, API keys in headers, and Basic Auth are all immediately readable.

Tip: Copy the cURL from your browser's Network tab (right-click a request → Copy → Copy as cURL) to get the exact headers and body the browser is sending, including cookies.

2

Format and validate the JSON request body

Paste the request body from your cURL command into the JSON Formatter & Validator. A surprising number of API failures are caused by malformed request JSON: a missing closing brace from a concatenated template string, a trailing comma that's valid in JavaScript but invalid in JSON, a numeric value that was serialised as a string ('42' vs 42), or a null value sent as the string 'null'. The formatter will immediately highlight any syntax error with the line number. After fixing syntax, visually inspect the structure: are required fields present? Are nested objects at the correct depth? Are array items the right type? Are field names spelled correctly (camelCase vs snake_case — the API's convention, not yours)? This step alone resolves 20–30% of mysterious 400 Bad Request errors.

Tip: Compare your request body structure against the API's sample request in its documentation. JSON Formatter makes this side-by-side comparison easy — open two tabs with the formatter.

3

Decode and inspect your JWT for expiry, audience, and algorithm issues

Most 401 Unauthorized errors come from JWT problems. Paste your Authorization Bearer token into the JWT Decoder to see the full decoded header, payload, and signature without needing a secret key. Check: (a) exp (expiry) — is the timestamp in the past? JWT expiry is a Unix timestamp; a value of 1700000000 expired in November 2023. (b) iss (issuer) — does it match what the API expects? (c) aud (audience) — some APIs reject tokens issued for a different audience even if the signature is valid. (d) alg (algorithm) in the header — beware of tokens with alg: 'none' (a well-known vulnerability), and RS256 tokens being sent to endpoints expecting HS256. (e) Scope or permissions claims — the token may be valid but missing the required scope for this specific endpoint.

Tip: JWT signatures can be verified without the private key if you have the public key — paste both into the decoder. Without the key, you can still read all claims and diagnose most auth failures.

4

Parse rate limit response headers to diagnose throttling

If your requests are failing intermittently with 429 Too Many Requests, or succeeding but slower than expected, paste the response headers from a failed request into the Rate Limit Header Parser. Standard rate limit headers include: X-RateLimit-Limit (max requests per window), X-RateLimit-Remaining (requests left in current window), X-RateLimit-Reset (Unix timestamp when the window resets), Retry-After (seconds to wait before retrying — set on 429 responses), and RateLimit-Policy (RFC 6585 format). Different APIs use different header names: GitHub uses X-RateLimit-*, Stripe uses Rate-Limit-Remaining, and some APIs use the standard Retry-After only on 429s. The parser normalises these into readable values so you know exactly when your quota resets and how much headroom you have.

Tip: In production code, always read and respect Retry-After headers before retrying — exponential backoff without checking Retry-After often means you're still retrying too fast.

5

Document the corrected endpoint for your team

Once the request is working, use the REST Endpoint Documenter to produce a clean, shareable record of the correct endpoint: URL, HTTP method, required headers (with example values, not real credentials), request body schema with field descriptions and types, successful response structure, and error codes with their meanings. This documentation serves two purposes: (a) immediate — sharing the working request with teammates who hit the same issue, (b) long-term — building an internal API reference so the same debugging session doesn't need to happen again in 6 months. The documenter exports in Markdown or OpenAPI format, which can be committed to your repository alongside the integration code.

Tip: Document the error states alongside the success state — note which response body field indicates the specific error, which status code maps to which problem, and how authentication failures differ from authorisation failures in the response.

Why this workflow works

API debugging is most efficient when it follows OSI-like layer ordering: format first (is the request structurally valid?), authentication second (is the caller who they claim to be?), authorisation third (does the caller have permission for this resource?), and infrastructure last (is rate limiting or a network issue the constraint?). This workflow mirrors that order. Developers often jump to JWT inspection (Step 3) immediately on a 401 error, but 20–30% of auth failures are actually malformed request bodies (Step 2) that cause the server to reject the request before even checking the token. Running all five steps systematically is faster than jumping around because each step eliminates an entire category of failure cause.

Frequently asked questions

What is the most common cause of API authentication failures?

In order of frequency: (1) Expired JWT — the token's exp claim is in the past; regenerate it. (2) Wrong Authorization header format — 'Bearer TOKEN' not 'Token TOKEN' or 'Basic TOKEN'. (3) Incorrect token scope — the JWT is valid but doesn't include the permission scope for this endpoint. (4) Wrong audience (aud claim) — a token issued for staging is being used against production. (5) Clock skew — the server's clock and the client's clock differ by more than the tolerance (usually 5 minutes for JWT validation). (6) Revoked token — the token was manually invalidated before expiry. The JWT Decoder surfaces all of these in one view.

What is the difference between a 401 and a 403 error?

401 Unauthorized means the request lacks valid authentication credentials — you either didn't provide a token, provided an expired token, or provided an invalid token. The server can't verify who you are. 403 Forbidden means the server successfully authenticated you but you don't have permission to access this specific resource. You're known, but not allowed. Debugging path: 401 → check JWT validity (Step 3 in this workflow). 403 → check token scope, user role, resource ownership, or IP allowlist. A common confusion: some APIs return 403 for expired tokens instead of 401, which can misdirect debugging toward permission issues when the real problem is authentication.

How do I decode a JWT without sending it to an online tool?

A JWT is three base64url-encoded segments separated by dots: header.payload.signature. You can decode the header and payload locally: in Python — import base64, json; parts = token.split('.'); json.loads(base64.urlsafe_b64decode(parts[1] + '==')). In JavaScript/Node — JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString()). Note: this decodes the payload claims without verifying the signature. Signature verification requires the secret (HS256) or public key (RS256/ES256). The JWT Decoder in this workflow does both decoding and signature verification if you provide the key.

How do I handle rate limit errors in production code?

Best practice: (1) Read Retry-After header — wait exactly that many seconds before retrying, not less. (2) Implement exponential backoff with jitter — base delay × 2^attempt + random(0–1000ms). This prevents thundering herd when many clients retry simultaneously. (3) Track X-RateLimit-Remaining proactively — when it drops below a threshold (e.g., 10% of limit), slow down requests before hitting 429. (4) Implement a circuit breaker — after N consecutive 429s, stop sending requests for a defined period. (5) Queue and batch requests if the API supports it — bulk endpoints have higher rate limits than per-item endpoints. (6) Consider request-level caching — return cached responses for repeated identical requests.

What is the best way to share a working API endpoint with my team?

Options in order of preference: (1) REST Endpoint Documenter → export as OpenAPI YAML and commit to your repository alongside the integration code. (2) A Postman collection exported as JSON and shared via version control. (3) An internal wiki page with the request example in a code block. (4) A Markdown file in your repo's /docs folder. Avoid: Slack/Teams messages (unsearchable, disappear), email (siloed), and undocumented shared Postman workspaces (no version history). The goal is: in 6 months, someone encountering the same API can find the working example without needing to reverse-engineer or re-debug it.

More workflows