cURL Works but fetch Fails: Compare CORS, Cookies and JSON
Convert a cURL request to fetch, compare method, headers and body, and separate browser CORS failures from HTTP errors and JSON parsing failures.
Maintained by Ali Aygör. Report a correction.
Start with a request you can compare
A successful terminal request and a failing browser request may reach the same URL through different rules. Before changing the server, compare the URL, method, request headers and body. Replace real credentials with placeholders in your investigation notes. The example below uses a synthetic order and an example.com URL; it is a conversion fixture, not a working order service.
Paste it into cURL to Fetch and check that POST, the JSON content type and the two body fields survive conversion. Compare the same command with cURL to Java if a backend integration works while the frontend fails. The converters generate code; they do not send this request or implement every cURL option.
curl 'https://example.com/v1/orders' \
-X POST \
-H 'Content-Type: application/json' \
-H 'X-Request-ID: demo-order-42' \
--data-raw '{"sku":"demo","quantity":2}'Find whether the browser sent the POST
Inspect the browser Network panel before retrying. A cross-origin JSON POST commonly has an OPTIONS preflight. If the browser rejects that preflight, investigate the allowed origin, method and requested headers. If POST received a 500, examine that server error as well; a missing CORS header can keep JavaScript from reading its body.
The terminal client does not apply browser CORS checks. Adding mode: 'no-cors' is not a way to read the response: it produces an opaque response with inaccessible content. A credentialed cross-origin request needs an explicit allowed origin and credential permission, rather than a wildcard origin. Use the HAR analyzer to distinguish an absent recorded response from a real HTTP error.
Compare authentication separately
Check whether the original request used a cookie, an Authorization header, a client certificate or another mechanism. A conversion that preserves the JSON body still cannot manufacture the browser's authentication state. Browser cookies and forbidden request headers have their own rules; copying a Cookie header into fetch is not a replacement for those rules.
For an authorized cookie-based integration, review the fetch credentials setting and the cookie's SameSite and Secure attributes alongside the server configuration. Do not add credentials to unrelated endpoints simply to make an error disappear. Keep the original and converted request beside one another so a missing header or changed body is visible.
Test HTTP status and JSON parsing independently
The converter's generated response.json() call is a starting point. Add handling that matches the real API: fetch can resolve with a 404 or 500, a 204 has no JSON body, and a proxy may send HTML. The function below accepts a Response, so the checks can run with synthetic responses without contacting any service.
The tests should cover a successful object, an empty 204, an HTTP failure and an HTML response. Record which layer fails: request construction, browser access, HTTP status or parsing. That record is more useful than treating every failure as a CORS problem.
async function readJsonResponse(response) {
if (!response.ok) throw new Error("HTTP " + response.status);
if (response.status === 204) return null;
const type = (response.headers.get("content-type") ?? "")
.split(";")[0].trim().toLowerCase();
if (type !== "application/json" && !type.endsWith("+json")) {
throw new Error("Expected JSON, received " + (type || "no content type"));
}
return response.json();
}
const demo = new Response('{"ok":true}', {
status: 200, headers: { "Content-Type": "application/json" }
});
console.log(await readJsonResponse(demo)); // { ok: true }Verify the same failing case after the fix
Repeat the original browser action with the same redacted payload and expected authentication state. Confirm that the application request runs, JavaScript can read the response and the response matches the contract. If the service returns an error object, preserve its request identifier in the debugging record rather than repeatedly submitting an order.
Keep a small fixture for request conversion and separate tests for status and parsing. These checks catch future body or header changes even when the endpoint happens to be available during a manual test.
Specifications and references
Continue the investigation
HAR Status 0 vs HTTP 500: Find Where an API Request Failed
Use browser network captures to distinguish an absent HTTP response, a CORS failure and a server 500, then correlate the request with logs.
Debug API JSON Responses: HTML Errors, Empty Bodies and Wrong Types
Debug JSON parsing failures with repeatable examples for HTML error responses, 204 empty bodies, string-versus-object payloads and valid JSON that violates an API contract.
How to Analyze HAR Files for API Debugging
A practical workflow for reading HAR files, finding slow requests, spotting 4xx/5xx failures, and sharing safe network summaries.
Related tools
Converters
OpencURL to fetch
Convert common cURL commands into readable JavaScript fetch examples for frontend work.
Converters
OpencURL to Java
Turn common cURL commands into readable Java HttpClient examples with headers and body preserved.
Dev Debug Helpers
OpenHTTP Header Analyzer
Parse raw headers and highlight content types, auth hints, caching directives, and likely debug concerns.
Dev Debug Helpers
OpenHAR File Analyzer
Analyze browser HAR files for slow requests, failed calls, duplicates, domains, and captured request timing.