API Bug Reports: Request, Response, and a curl Anyone Can Run

Written By  Crosscheck Team

Content Team

August 6, 2026 8 minutes

API Bug Reports: Request, Response, and a curl Anyone Can Run

API bug reports: request, response, and a curl anyone can run

A backend developer opens a ticket that says "invoice API is failing". No URL. No status code. No body. They reply "which endpoint?" and the ticket sits for two days.

An API bug report has one job: let another person reproduce the failure on their own machine in under a minute. If they have to guess anything, you have not finished the report.

Short version

  • Record the full request: method, URL, headers, and body.
  • Record the full response: status code, key headers, and body.
  • Paste a runnable curl command so nobody retypes anything.
  • Include the environment, the timestamp, and the request ID.
  • Replace secrets with REDACTED, but keep the shape of the value.
  • Never paste a real token, password, or customer email into a ticket.

What an API bug report must contain

An API, or Application Programming Interface, is the set of URLs your app calls to read and write data. A bug in an API is almost always one of three things: the wrong status code, the wrong response body, or no response at all.

To prove which one it is, your report needs five parts:

  1. The request you sent.
  2. The response you got.
  3. The response you expected.
  4. A curl command that repeats step 1.
  5. Environment and timing details.

That is it. Everything else is optional.


Capture the full request

Open your browser DevTools, go to the Network tab, right-click the failing call, and copy it. In most APIs there are four things you need.

Method and URL. Write them on one line, together:

POST https://api.staging.example.com/v1/invoices

Do not write "the invoices endpoint". There may be four of them.

Headers. You only need the ones that change behaviour: Authorization, Content-Type, Accept, Idempotency-Key, and any custom X- header your app sends.

Body. Paste the exact JSON you sent, not a description of it. This is the difference between a fix and a guessing game:

Bad: sent an invoice with a numeric ID

Good: {"invoice_id": 4021, "amount": 4999, "currency": "usd"}

Query parameters. Keep them in the URL. ?limit=50&status=open is part of the request.


Capture the full response

The response is where the answer usually hides, and it is the part people skip most often.

Record three things:

  • Status code, such as 401 Unauthorized. Write the number and the text.
  • Response headers that help triage: x-request-id, retry-after, content-type.
  • Response body, copied exactly, including the quotes and braces.

A real example:

401 Unauthorized
x-request-id: req_8f2a91c4
content-type: application/json

{"error":"invoice_id must be a string"}

That body is interesting on its own. A 401 normally means an auth problem, but the message talks about a field type. That mismatch is often the real bug, and a developer can only see it if you paste both parts.

Then add one line for what you expected:

Expected: 201 Created with the new invoice object.


Turn it into a curl command anyone can run

curl is a command line tool that sends HTTP requests. A curl command is the most portable bug repro there is. It works on any machine, needs no setup, and cannot be misread.

Most browsers build it for you:

  1. Open DevTools and select the Network tab.
  2. Reproduce the bug so the failing call appears.
  3. Right-click the call and choose Copy then Copy as cURL.
  4. Paste it into a text editor.
  5. Remove cookies and browser noise, then redact secrets.
  6. Test the cleaned command yourself before you file the ticket.

Step 6 matters. A curl command that does not run is worse than none, because it sends someone down a false path.

Here is a clean one:

curl -i -X POST https://api.staging.example.com/v1/invoices \
  -H "Authorization: Bearer sk_test_REDACTED" \
  -H "Content-Type: application/json" \
  -d '{"invoice_id": 4021, "amount": 4999, "currency": "usd"}'

The -i flag prints the response headers, so whoever runs it sees the status code and the request ID without extra work. Keep the line breaks with \ so the command stays readable in the ticket.


What the status code tells your team

The status code is a three-digit number the server returns. It decides who should look at the bug first, so put it in your ticket title.

Status codeWhat it usually meansWho should look first
400 Bad RequestThe request body or parameters are wrongWhoever built the caller (often frontend)
401 UnauthorizedMissing, expired, or invalid credentialsThe person testing, then auth owner
403 ForbiddenCredentials are valid but lack permissionBackend or permissions owner
404 Not FoundWrong URL, or the record does not existReporter checks the URL, then backend
409 ConflictDuplicate or state clash, such as a repeated invoiceBackend
422 UnprocessableBody parses, but a value fails validationBackend and frontend together
429 Too Many RequestsRate limit hitBackend or infrastructure
500 Internal Server ErrorThe server crashed on this requestBackend, high priority
502 / 503 / 504Gateway, deploy, or timeout problemInfrastructure or on-call

Two pairs cause most confusion.

401 vs 403. 401 means the server does not know who you are. 403 means it knows and still says no. If you see 403, mention the account and role you used: [email protected] with the Viewer role.

400 vs 500. A 400 is usually the caller's mistake. A 500 is always a server bug, even when the request was wrong, because a server should reject bad input politely instead of crashing. Never close a 500 as "invalid input".


Redact secrets without breaking the repro

Bug trackers are searchable, exported, and often shared with contractors. Treat every ticket as public.

The rule: keep the shape, remove the value. Shape tells the developer whether you used the right kind of key. The value is what leaks.

Do not pastePaste this instead
Authorization: Bearer sk_live_51Hq...Authorization: Bearer sk_test_REDACTED
X-Api-Key: 8f14e45fceea167aX-Api-Key: REDACTED
"email":"[email protected]""email":"[email protected]"
"card_number":"4242424242424242""card_number":"REDACTED"
A full session cookie headerRemove the line and add a note

Three more habits worth keeping:

  • Keep the prefix. sk_test_REDACTED says you used a test key. Plain REDACTED does not, and the reviewer may waste time asking.
  • Use staging accounts. Test with [email protected] on https://staging.example.com so there is nothing sensitive to hide.
  • Rotate anything that slips. If a live token reaches a ticket, revoke it. Editing the comment does not undo the exposure.

When your bug starts in the browser rather than in a terminal, Crosscheck captures the failing network requests, console errors, and environment details with the report, so you have the request and response side by side before you write the curl.


Environment and timing details

Backend engineers search logs. Logs need anchors. Give them four:

  • Environment: staging, production, or a preview URL. Name it exactly: api.staging.example.com.
  • Timestamp in UTC: 2026-08-06 14:32:10 UTC. Local time costs someone a conversion.
  • Request ID or trace ID: req_8f2a91c4, taken from the x-request-id response header. This is the single most valuable line in an API bug report. It takes a developer straight to the exact log entry.
  • Frequency: every time, or 3 of 10 attempts. Intermittent bugs are triaged differently.

If your API does not return a request ID header, ask for one. It is a small change that saves hours on every future report.


A copy-paste API bug report template

Title: POST /v1/invoices returns 401 with a validation message on staging

Environment: staging (api.staging.example.com)
Time: 2026-08-06 14:32:10 UTC
Account: [email protected] (Admin)
Request ID: req_8f2a91c4
Frequency: every attempt (5 of 5)

Request
POST https://api.staging.example.com/v1/invoices
Content-Type: application/json
Authorization: Bearer sk_test_REDACTED

{"invoice_id": 4021, "amount": 4999, "currency": "usd"}

Response
401 Unauthorized
x-request-id: req_8f2a91c4

{"error":"invoice_id must be a string"}

Expected
201 Created with the new invoice object.

Repro
curl -i -X POST https://api.staging.example.com/v1/invoices \
  -H "Authorization: Bearer sk_test_REDACTED" \
  -H "Content-Type: application/json" \
  -d '{"invoice_id": 4021, "amount": 4999, "currency": "usd"}'

Notes
Sending "invoice_id": "4021" as a string returns 201. The status code
does not match the error message.

That last note is the kind of detail that closes a bug the same day. You narrowed the trigger to one field, and you said which status code looks wrong.


Frequently asked questions

What if the API returns no response at all? Say so, and include how long you waited before it timed out. A 30 second hang with no status code points at a different problem than a fast 500.

Should I include the response time? Include it when speed is the bug, or when the call is unusually slow. 4.2 seconds is a fact; "slow" is an opinion.

Can I attach a Postman collection instead of curl? You can attach it as well, but keep the curl in the ticket body. Not everyone has Postman open, and text is searchable years later.

How do I report a bug I can only reproduce while logged in? Use a shared test account such as [email protected], note the role, and redact the token. Say which login step produces the credentials.

Is the request ID really that important? Yes. Without it, a developer scans thousands of log lines by timestamp. With it, they find the exact request and its stack trace in seconds.

Related Articles

Contact us
to find out how this model can streamline your business!

Trusted by thousands ofengineering teams worldwide.

Add to Chrome
200+ reviews · 100k+ users
Crosscheck browser extension capture controls

Join the Crosscheck Community

Stay in the loop with Crosscheck's newest features and insights.