Generate curl Snippets from OpenAPI to Reproduce API Bugs
Turn OpenAPI operations into accurate curl commands, attach auth headers correctly, and use snippets to reproduce frontend API failures for backend teammates.
Understand browser CORS, OPTIONS preflight, Access-Control-Allow-Origin, and credentials mode — plus the SPA misconfigurations that waste hours in production debugging.
By codefunc
Cross-Origin Resource Sharing (CORS) is not a server firewall and not an authentication system. It is a browser enforcement layer: your SPA at https://app.example.com asks for data from https://api.example.com, and the browser refuses to expose the response to JavaScript unless the API explicitly allows that origin. Misconfigured CORS produces opaque console errors, broken login flows, and "it works in curl" confusion that burns sprint time.
Bottom line: CORS is enforced by the browser, not by curl or Postman. Fix Access-Control-Allow-Origin and preflight headers on the API, match credentials mode carefully, and never treat * as a shortcut when cookies or Authorization are involved.
Frontend and full-stack engineers shipping SPAs, Next.js apps with a separate API origin, or local setups where localhost:3000 talks to localhost:8080. If you have ever seen No 'Access-Control-Allow-Origin' header is present and then proved the endpoint "works" with curl, this guide is for you.
A request is cross-origin when scheme, host, or port differ from the page that issued it. Same-origin examples:
| Page origin | Request URL | Cross-origin? |
|---|---|---|
https://app.example.com |
https://app.example.com/api |
No |
https://app.example.com |
https://api.example.com/v1 |
Yes (host) |
http://localhost:3000 |
http://localhost:8080/v1 |
Yes (port) |
https://app.example.com |
http://app.example.com |
Yes (scheme) |
The browser sends the request (in most cases), then decides whether your JavaScript may read the response. Server logs may show a successful HTTP 200 while DevTools still reports a CORS failure — because the failure is at the browser boundary, not at your load balancer.
Browsers classify some cross-origin requests as "simple." Those skip the OPTIONS round trip. Everything else triggers a preflight.
GET, HEAD, POSTAccept, Accept-Language, Content-Language, and Content-Type only if it is application/x-www-form-urlencoded, multipart/form-data, or text/plainAuthorization or X-Request-IdPUT, PATCH, DELETE, and others outside the simple setContent-Type: application/json (extremely common for SPAs)Authorization, X-CSRF-Token, X-Api-Key, etc.Preflight is an OPTIONS request asking: "If the real request uses method M and headers H, will you allow origin O?" The server must answer with the right Access-Control-* headers before the browser sends the actual POST/PUT.
| Header | Role |
|---|---|
Access-Control-Allow-Origin |
Which origin may read the response (https://app.example.com or, in limited cases, *) |
Access-Control-Allow-Methods |
Methods allowed for the actual request (preflight) |
Access-Control-Allow-Headers |
Request headers the client may send (preflight) |
Access-Control-Allow-Credentials |
Whether cookies / credentialed mode is allowed (true) |
Access-Control-Expose-Headers |
Response headers JS may read beyond the safe defaults |
Access-Control-Max-Age |
How long the browser may cache the preflight result |
Generate a correct set with the CORS Header Generator, then paste values into your API gateway, Express middleware, or CDN config. Validate raw responses with the HTTP Header Parser.
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Vary: Origin
Notes:
* with Allow-Credentials: true.Vary: Origin when you echo the request Origin so caches do not serve the wrong allowlist to another site."Credentials" in CORS means the browser may include cookies, client certificates, and (in some modes) HTTP auth on cross-origin calls. In fetch:
await fetch("https://api.example.com/me", {
credentials: "include", // omit | same-origin | include
headers: { "Content-Type": "application/json" },
});
| Client setting | Server must allow | Common failure |
|---|---|---|
credentials: "include" |
Specific Allow-Origin + Allow-Credentials: true |
Using * for origin |
| Cookie session SPA | Same as above + correct SameSite / domain on the cookie |
Cookie never sent |
Bearer token in Authorization |
Preflight must allow Authorization |
Header blocked by preflight |
Bearer tokens in headers do not require credentials: "include" by themselves, but they do trigger preflight. Cookie-based sessions almost always need both include and Allow-Credentials: true.
curl and Postman do not enforce CORS. A successful:
curl -i https://api.example.com/v1/items \
-H "Content-Type: application/json" \
-H "Authorization: Bearer …"
proves the API is up and authenticated for that token. It does not prove a browser at another origin can read the response. Reproduce the browser path by inspecting the failing Network entry: look at the OPTIONS preflight status, response CORS headers, and the subsequent actual request. Build an equivalent probe with the curl Command Generator when you need a shareable repro for backend teammates — then remind them to compare against the browser's Origin and preflight, not only the happy-path GET.
* with credentialsBrowsers reject this combination. Fix: allowlist exact frontend origins (prod, staging, and local if needed).
Your POST /v1/orders handler returns JSON, but the reverse proxy returns 404 or 405 on OPTIONS. Preflight fails; the SPA never reaches POST. Ensure every CORS-enabled route answers OPTIONS with 204/200 and the allow headers.
You allow Content-Type but the client also sends Authorization or a tracing header. Preflight fails with a console message about a disallowed header. Mirror the headers your SPA actually sends.
http://localhost:3000 and http://127.0.0.1:3000 are different origins. Vercel/Netlify preview URLs change per deploy. Prefer an allowlist driven by config, not a single hard-coded string that breaks every PR preview.
You return X-Request-Id for support, but response.headers.get("X-Request-Id") is null in JS. Add Access-Control-Expose-Headers: X-Request-Id.
CORS does not stop a malicious site from triggering a simple request in some cases, and it does not stop non-browser clients at all. Protect APIs with real authentication and authorization. CORS only controls which web pages can read responses in a user's browser.
Allow-Headers: * and broad method lists are convenient in development. In production, prefer an explicit list that matches your client so unexpected header injection is visible during preflight failures.
OPTIONS precedes it.Origin matches what you intended to allowlist.Allow-Origin, Allow-Methods, and Allow-Headers.Allow-Origin again (both responses need correct CORS headers).Allow-Credentials, cookie SameSite, Secure, and domain.true in production.fetch in RSC / Node: no CORS. CORS applies to browser-mediated calls.Debug CORS in the browser Network panel, not only with curl. Separate preflight failures from actual-request failures. Use a specific Access-Control-Allow-Origin whenever credentials or cookie sessions are involved. Allowlist methods and headers your SPA really sends, answer OPTIONS correctly, and keep auth as a separate concern. Use codefunc's CORS Header Generator, curl Command Generator, and HTTP Header Parser to draft, reproduce, and inspect policies without pasting production secrets into third-party servers.
Turn OpenAPI operations into accurate curl commands, attach auth headers correctly, and use snippets to reproduce frontend API failures for backend teammates.
Configure CSP for single-page apps — script-src with nonces and hashes, common React and Vite failures, CORS interplay, reporting, and a practical rollout checklist.
Get Content-Type right for APIs, file uploads, and static assets — charset parameters, multipart boundaries, sniffing risks, and a practical MIME lookup workflow.
Find a developer tool