Debugging HTTP API requests step by step
When an API call fails, it is tempting to start changing code until something works. A more reliable approach is to treat the request as data: an HTTP request is only a method, a URL, some headers and an optional body, and the response is a status code, headers and a body. If you check each of those pieces in a fixed order, almost every integration problem becomes obvious within minutes. This guide describes that order.
Step 1: Capture the exact request
You cannot debug a request you have not seen. Do not rely on what the code is supposed to send; look at what it actually sent.
- In a browser, open the developer tools Network tab, find the request and look at its headers, payload and response. Most browsers can copy a request as cURL from the context menu.
- In backend code, log the method, full URL, headers (with secrets masked) and body just before sending.
- Between services, a proxy log or an API gateway trace often shows what really arrived.
Then rebuild that request by hand in a tool such as the API Request Builder or cURL. If the hand-built request fails the same way, you have a minimal reproduction that does not depend on your application. With cURL, -v prints everything that was sent and received, which is usually the fastest way to see the truth:
curl -v https://api.example.com/v1/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data '{"sku":"A-100","quantity":2}'
> POST /v1/orders HTTP/2 lines starting with > are what you sent
> content-type: application/json
< HTTP/2 422 lines starting with < are what came back
< content-type: application/problem+json
Note that --data makes cURL send a POST, and without the explicit header it would label the body application/x-www-form-urlencoded, which is exactly the kind of difference this step is meant to expose.
Step 2: Read the status code before the body
The status code tells you which side to investigate:
- 4xx means the server understood the request and rejected it. The problem is in what you sent: URL, auth, headers or body.
- 5xx means the server or something in front of it failed. Your request may still be wrong, but the server did not handle it gracefully.
- 3xx means a redirect. Clients that do not follow redirects, or that drop the body or Authorization header when they do, produce confusing failures.
If a code is unfamiliar, look it up in the HTTP status code reference. Distinguishing 401 from 403, or 502 from 504, immediately narrows the search.
Step 3: Check the URL
A surprising share of failures are simply the wrong URL. Check, in this order:
- Host and environment. Are you calling staging with a production key, or the other way round?
- Path. Version prefixes such as
/v1/, plural versus singular resource names, and trailing slashes. Some frameworks redirect/usersto/users/, and the redirect may turn a POST into a GET. - Query string encoding. A value containing
&,+,#or a space must be percent-encoded, or it will be split or truncated.
Pasting the URL into the URL Parser shows every parameter decoded on its own line, which makes encoding problems visible. The URL anatomy guide explains the rules.
Step 4: Check the method
A 405 Method Not Allowed is easy to recognise, but some APIs return 404 for an unsupported method on an existing path, which looks like a URL problem. Confirm the documented method, and remember that a GET request with a body is ignored or rejected by many servers and proxies.
Step 5: Check authentication
For 401 and 403 responses, inspect the credential the request actually carried.
- Is the header name and format right? Most APIs expect
Authorization: Bearer <token>, with exactly one space and the word Bearer. Some expect a custom header likeX-API-Key. - Has the token expired? If it is a JWT, decode it with the JWT Decoder and check
exp,aud,issand the scopes. - For Basic auth, the value is Base64 of
username:password. A newline accidentally included before encoding is a classic cause of failures. - Is the server's clock correct? Token validation and signed requests such as AWS Signature v4 fail if clocks drift too far.
Step 6: Check Content-Type and the body
For 400 and 422 responses, the server usually could not read or did not like your body.
- Content-Type must match the body. Sending JSON with
Content-Type: application/x-www-form-urlencoded, or with no Content-Type at all, makes many frameworks see an empty body and report "missing field". - The body must be valid. Paste it into the JSON Formatter to rule out syntax errors.
- Field names and types must match the schema. Common slips are camelCase versus snake_case, numbers sent as strings, and dates in the wrong format.
In browser code, the usual culprit is a fetch call that serialises the body but forgets the header. Without it, a string body is sent as text/plain;charset=UTF-8:
// Sent as text/plain: many servers see no JSON body at all
await fetch('/api/orders', { method: 'POST', body: JSON.stringify(order) });
// Correct
await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(order),
});
Read the response body carefully at this stage. Well-designed APIs say exactly which field failed validation.
Step 7: Understand CORS errors
If a request works in cURL but fails in the browser with a CORS error, the request itself is probably fine. The browser blocked your page from reading the response because the server did not send an Access-Control-Allow-Origin header that allows your origin. For requests with custom headers or JSON bodies, the browser first sends an OPTIONS preflight request, and that must succeed too.
CORS can only be fixed on the server, or by routing the call through your own backend. Adding headers on the client does not help. Also note that a CORS failure hides the real status code from your JavaScript, so a 500 on the server can show up only as a CORS error in the console.
Step 8: Look at what sits in between
When the request is correct and the API still fails, consider the layers between you and the application:
- 502 Bad Gateway usually means the proxy reached the app but got an invalid response, often because the app crashed or is listening on the wrong port.
- 504 Gateway Timeout means the app did not answer in time. Check slow queries and upstream calls.
- 413 Payload Too Large often comes from the proxy's body size limit, not the application.
- Corporate proxies, VPNs and TLS-inspecting firewalls can change headers or block traffic entirely.
HTTP status codes in practice lists the usual causes behind each of these codes in more detail.
Step 9: Change one thing at a time
Once you have a reproducible request, debugging becomes a controlled experiment. Change one header, one field or one parameter, resend, and compare. Start from a request that the API documentation says works, such as its own cURL example, and move step by step towards the request your application sends. The first change that breaks it is your bug.
Write it down
When you find the cause, save the working request as a cURL command in the ticket or the integration's README. The next person who sees the same failure, possibly you in six months, can then start from step 9 instead of step 1.