When a production API fails, the fastest path to a useful answer is a consistent sequence: confirm the symptom, isolate the failing layer, compare a known-good request, and preserve evidence before changing anything. This API debugging runbook provides that sequence, a one-page incident checklist, copy-ready curl and Postman templates, practical observability queries, and a maintenance schedule for keeping the guidance useful as your stack changes.
Overview
API incidents are often described too broadly: “the endpoint is down” or “the service returns 500.” Begin by turning that description into a bounded technical problem. Record the endpoint, HTTP method, status code, time window, affected environment, request identifier, and whether the failure is consistent or intermittent.
The diagnostic order matters. Start at the client and edge, then move toward application code, dependencies, data, and infrastructure. This avoids spending time inspecting application logs when a request is being rejected by a gateway, or debugging a database query when the client is sending an invalid token.
- Confirm the symptom: Reproduce the failure with the smallest safe request and note the exact response.
- Scope the impact: Compare endpoints, regions, tenants, methods, status codes, and recent deploys.
- Trace one request: Use a correlation or request ID to follow the call through the edge, application, and dependencies.
- Compare known-good traffic: Diff headers, parameters, payload shape, identity, route, and timing.
- Mitigate carefully: Roll back, disable a feature, reduce traffic, or fail over only when the action is understood and reversible.
- Verify recovery: Retest the original failure and watch error rate, latency, saturation, and dependency health.
For REST APIs, inspect the method, path, query string, headers, status code, and response body. For GraphQL, also record the operation name, variables, selected fields, and the distinction between an HTTP success response and an errors array in the response body. A GraphQL request can reach the application successfully while an individual resolver fails.
What to track
Request facts
Capture facts before forming a theory. A useful incident record includes:
- UTC timestamp and the client’s local time, if relevant
- Environment, region, host or service version, and deployment identifier
- HTTP method, route template, status code, and response size
- Request ID, trace ID, and upstream or downstream IDs
- Authentication method and authorization result, without copying secrets
- Latency split by client, gateway, application, and dependency where available
- Whether the request is reproducible, tenant-specific, payload-specific, or time-dependent
Do not paste access tokens, cookies, API keys, passwords, or personal data into tickets or chat. Redact sensitive values while retaining enough structure to compare requests.
Signals by diagnostic layer
| Layer | Track | Useful clue |
|---|---|---|
| Client | URL, method, headers, payload, timeout | Only one client or request shape fails |
| Edge | DNS, TLS, gateway status, rate limits | Failure occurs before an application trace exists |
| Application | Route logs, exceptions, validation, deploy version | Errors cluster around a code path or release |
| Dependencies | Database, queue, identity provider, third-party latency | Application timeouts align with an upstream slowdown |
| Infrastructure | CPU, memory, connection pools, disk, network | Errors rise with saturation or exhausted limits |
Choose the smallest tool that answers the question
| Tool | Best use | Limit |
|---|---|---|
curl | Repeatable requests, headers, timing, and exit-code checks | Less convenient for large collections or team sharing |
httpie | Readable interactive requests and JSON output | May not be installed in a minimal incident environment |
| Postman | Saved environments, auth flows, examples, and collaboration | Be careful with synced secrets and stale variables |
tcpdump | Investigating transport-level behavior when authorized | Payloads may contain sensitive data; capture narrowly and securely |
| Tracing UI | Following one request across services and dependency spans | Sampling or missing propagation can leave gaps |
| Log search | Finding patterns across time, status, route, and request IDs | Unstructured or inconsistent fields slow investigation |
Copy-ready request templates
Replace placeholders, then remove or redact sensitive values before sharing output. The --connect-timeout and --max-time values below are starting points, not universal production defaults; align them with your API’s normal latency and client contract.
curl --request GET \
--url 'https://api.example.test/v1/orders?limit=10' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer REDACTED_TOKEN' \
--header 'X-Request-ID: debug-REPLACE_ME' \
--connect-timeout 5 \
--max-time 30 \
--include \
--silent \
--show-error
For a JSON request:
curl --request POST \
--url 'https://api.example.test/v1/orders' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer REDACTED_TOKEN' \
--header 'X-Request-ID: debug-REPLACE_ME' \
--data '{"sku":"example-item","quantity":1}' \
--connect-timeout 5 \
--max-time 30 \
--include \
--silent \
--show-error
In Postman, create an environment with baseUrl, accessToken, and requestId variables. Use this request configuration:
GET {{baseUrl}}/v1/orders?limit=10
Headers:
Accept: application/json
Authorization: Bearer {{accessToken}}
X-Request-ID: {{requestId}}
For repeatable checks, add a test that records the status and response time without assuming every endpoint returns the same code:
pm.test("response has an HTTP status", function () {
pm.expect(pm.response.code).to.be.a("number");
});
pm.test("response time is within the endpoint budget", function () {
pm.expect(pm.response.responseTime).to.be.below(30000);
});
Adjust the time budget to the endpoint’s documented contract. A broad test is useful for smoke testing, but it should not replace assertions about the expected status, schema, or error format.
Cadence and checkpoints
Use the runbook during three recurring activities: live incidents, post-mortems, and pre-release smoke tests. During an incident, update the timeline as facts arrive rather than relying on memory. In a post-mortem, attach the request, trace, log query, and mitigation that established the cause. Before release, run the same representative requests against the target environment and confirm that failures are observable.
One-page incident checklist
- Declare: Name the affected service, start time, incident owner, and communication channel.
- Reproduce: Save one failing request and one known-good comparison, with secrets removed.
- Scope: Check whether the issue affects all routes, one route, one method, one tenant, or one region.
- Check the edge: Inspect DNS, TLS, gateway responses, rate limits, and load-balancer health.
- Follow the ID: Search logs and traces using the request or trace ID.
- Compare versions: Check deployments, configuration changes, migrations, and feature-flag changes.
- Check dependencies: Compare database, queue, identity, and third-party latency or error signals.
- Mitigate: Choose the smallest reversible action and record who approved it.
- Verify: Repeat the failing request and confirm service-level signals improve.
- Close the loop: Preserve evidence, document the cause, and create follow-up work for prevention.
Example log searches should be adapted to your platform’s field names. In a fielded system, search by exact request ID first:
service:orders request_id:"debug-REPLACE_ME"
service:orders status:500 route:"/v1/orders" earliest:-30m
service:orders (timeout OR "connection refused") earliest:-30m
In a SQL-backed request log, the equivalent investigation might look like:
SELECT timestamp, status_code, route, latency_ms, request_id, error_type
FROM api_requests
WHERE service = 'orders'
AND timestamp >= CURRENT_TIMESTAMP - INTERVAL '30 minutes'
ORDER BY timestamp DESC;
Use your actual query language and timestamp syntax. When searching Elastic, Datadog, Honeycomb, or another observability platform, preserve the same logical filters: service, route, status, time window, and request or trace ID.
How to interpret changes
Read status codes as clues, not diagnoses. A 4xx response often points toward client input, identity, permissions, routing, or rate limits; verify the response body and gateway logs before assigning ownership. A 5xx response indicates that the server-side path did not complete successfully, but the cause may still be a dependency timeout, invalid data, exhausted connection pool, or deployment change. A 2xx response with an incorrect payload requires schema, data, and business-logic checks.
Separate error rate from latency. Rising latency with stable errors can indicate saturation or a slow dependency. Stable latency with a sudden error increase can point to validation, authorization, routing, or a code-path regression. A change limited to one tenant or payload shape suggests data or feature variation rather than a platform-wide outage.
Use traces to locate the longest span and the first error, but do not assume the longest span is the root cause. A downstream timeout may be the visible symptom of a resource limit elsewhere. Correlate the trace with deployment time, logs, dependency metrics, and the request’s actual payload.
Time zones create avoidable confusion during investigations. Store and search incident timestamps in UTC, then record any local-time interpretation separately. For more guidance on reliable timestamp handling, see JavaScript dates and time zones.
When to revisit
Review this runbook every six to twelve months, and sooner after a notable API outage or a post-mortem that exposes a missing diagnostic step. A quarterly review works well for teams with frequent releases or changing observability systems; a six- to twelve-month review may be sufficient for a stable internal API.
Refresh the article and your local incident copy when any of these change:
- CLI syntax, Postman behavior, or supported runtime versions
- Node/Express, Django, Nginx, gateway, authentication, or timeout configuration
- Log field names, trace propagation, sampling, dashboards, or query syntax
- Elastic, Datadog, Honeycomb, Jaeger, or another observability platform’s filtering workflow
- Recommended timeout, retry, concurrency, or rate-limit values in your service contracts
- Common response formats, error codes, redaction rules, or incident escalation paths
At each checkpoint, run the templates against a safe test endpoint, confirm that the example queries return the intended fields, and remove assumptions that no longer match your stack. Keep a dated change note so on-call engineers know which commands and dashboards were validated.
Action for your next shift: copy the checklist into your incident template, create a redacted Postman environment, and save one curl request for a known-good health or read-only endpoint. Then test the full path during your next pre-release smoke test. These small preparations turn an unfamiliar API failure into a bounded investigation with reusable evidence.