Your API Works in cURL. Why Does the Browser Reject It?
The API returns JSON in your terminal. Your frontend gets a network error. Before changing the payload again, compare the two requests: the browser may be asking permission to send a request that cURL sends directly.
That difference gives you a useful starting point. Keep the successful request as a baseline, then investigate the browser exchange separately.
Find the request that actually failed
Open the browser Network panel, reproduce the failure, and look for OPTIONS immediately before your API call. A cross-origin request with Content-Type: application/json or an Authorization header normally requires a preflight. If that preflight fails, the intended request may never reach your handler.
Start a small incident note with the page origin, destination URL, intended method, requested headers, and the first failing status. Include which layer produced it: your application, gateway, or reverse proxy. This is much more actionable than a screenshot of “Failed to fetch.”
Reproduce the preflight, not just the POST
Here is an illustrative probe for an API you control. Replace the example URL and origin with your own development environment:
curl -i -X OPTIONS 'https://api.example.com/widgets' \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: authorization,content-type'For this example, a successful preflight could return:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: authorization, content-type
Vary: OrigincURL displays the exchange; it does not enforce browser CORS rules. Use this probe to inspect headers, then verify the fix in the browser. MDN’s CORS guide explains the preflight flow in detail.
Check the response after the preflight, too
An OPTIONS success is only one checkpoint. The actual response needs the appropriate CORS headers as well. Test an expected failure, such as an expired test token: a gateway-generated 401 without those headers can obscure the error your frontend needs to handle.
For cookie-based cross-origin requests using credentials: "include", the server must allow credentials and return the specific allowed origin rather than *. Browser cookie policies still apply. A preflight generally does not carry the actual request’s credentials, so demanding a session cookie on OPTIONS can block the request before authentication even begins. Keep authentication on the actual operation.
Do not switch to mode: "no-cors" to make the console quieter. It produces an opaque response that your code cannot read like an ordinary JSON response. The Fetch standard defines these behaviors; changing the client flag does not grant permission to read the API.
Keep a tiny reproduction package
For the handoff, save three things: the working request, the preflight probe, and the browser failure details. Redact tokens and cookies. Record the actual page origin, including its port, so the next person can reproduce the same conditions.
We build Powerduck, a local OpenAPI workspace with cURL import and API debugging. It is useful for keeping the baseline request and its contract together. Browser DevTools remains essential here: a successful desktop request cannot certify that your website’s CORS configuration is correct.
Close the issue only after the real browser can read both a successful response and an expected error. That is the behavior your frontend depends on.