What this tool does that a proxy cannot
Almost every online "API tester" — including the one on our own homepage — works by
forwarding your request through a server. That is a deliberate and useful design: a server is not a
browser, so it ignores Access-Control-Allow-Origin completely and can call any public
endpoint. It is also the reason a proxied tester can tell you nothing whatsoever about CORS. By the time
the response reaches your browser it is coming from our origin, and the target's CORS policy has
been bypassed rather than tested.
This page does the opposite, on purpose. It calls fetch() from this page, in your browser,
with this page's origin, and lets the same-origin policy apply exactly as it would in your own front-end
code. The result is the only kind of answer that is actually worth having: can a browser page read
this resource, yes or no. Nothing else on this site can tell you that, and neither can curl.
The two tools are complementary rather than competing. Use the API Tester when you want to know what an endpoint returns — status, headers, body, timing — regardless of who is allowed to call it. Use this page when a request works everywhere except in the browser, and you need to find out whether CORS is the reason. Running both against the same URL is the quickest possible diagnosis: a clean 200 in the proxy plus a block here means the API is healthy and simply has not opted in to cross-origin access.
Why a CORS failure tells you almost nothing
This is the part that costs developers the most time, and it is worth stating precisely, because it is designed behaviour rather than a gap in the tooling.
When a cross-origin request is blocked, fetch() rejects with a
TypeError whose message is generic — usually just "Failed to fetch" or "NetworkError when
attempting to fetch resource". It carries no status code, no response headers, no indication of whether
the server answered at all. The same rejection appears whether the host does not resolve, the connection
was refused, the TLS handshake failed, the server returned
500, or the server returned a perfectly good
200 without an Access-Control-Allow-Origin header.
That is not laziness in the specification. Exposing the reason would itself be an information leak. A page that could distinguish "connection refused" from "responded but not allowed" could scan your private network from inside your browser, enumerate which internal hosts exist, and infer whether you are logged in to a third-party service — all from a site you merely visited. The browser therefore collapses every failure mode into one indistinguishable error before JavaScript ever sees it. The detailed explanation is printed to the developer console, where a human can read it and a script cannot; that asymmetry is the entire mechanism.
The practical consequence: when this tool reports a block, believe the outcome and ignore the
message. The next step is never to inspect the JavaScript error. It is to open your browser's
developer console for the exact wording, and then to look at the server's response headers using a client
that is not a browser — the proxied API Tester, or curl, or the
curl converter to build the command. What you are looking for is
whether Access-Control-Allow-Origin is present at all, and whether its value matches your
origin exactly.
Reading the three outcomes
| Outcome | What happened | What to do |
|---|---|---|
| Readable | The request completed and the browser handed the response to this page. The server sent an
Access-Control-Allow-Origin that covers this origin. |
Nothing — a front-end on this origin could call this endpoint. Note that a non-2xx status here is still a success as far as CORS is concerned. |
| Opaque | You chose no-cors. The request was probably sent, but the response is sealed: status
reads as 0, headers are empty, body unreadable. |
Only useful for fire-and-forget beacons or service-worker caching. It is not evidence that CORS works — you asked the browser not to check. |
| Blocked | The promise rejected with a bare TypeError. Could be CORS, DNS, TLS, a refused
connection, mixed content or an extension. |
Check the console for the real reason, then confirm the server's Access-Control-*
headers with a non-browser client. |
One subtlety on the readable case: CORS also restricts which response headers a page may read.
By default you get only the safelisted set — Cache-Control, Content-Language,
Content-Length, Content-Type, Expires,
Last-Modified and Pragma. Everything else is hidden unless the server names it
in Access-Control-Expose-Headers. This is why rate-limit headers and pagination
Link headers so often appear to be missing from a browser client while curl shows them
plainly. The tool lists exactly what it could read, which makes the gap visible.
Simple requests, preflight, and the rules that decide
A cross-origin request is either simple, in which case the browser sends it immediately
and checks the response headers afterwards, or it is not, in which case the browser first sends an
OPTIONS preflight to ask permission and sends nothing else until it gets a
satisfactory answer. Knowing which category you are in explains most confusing CORS behaviour, including
the classic "my GET works but my POST does not".
A request is simple only when all of the following hold:
- The method is
GET,HEADorPOST. - Every header your code sets is on the CORS-safelist:
Accept,Accept-Language,Content-Language,Content-Type, andRangewith a simple byte range. Headers the browser sets for itself do not count. Content-Type, if present, is one ofapplication/x-www-form-urlencoded,multipart/form-dataortext/plain.- No upload progress listener is attached to the request.
The Content-Type clause is where nearly everyone lands. application/json is not
on the safelist, so every JSON API call from a browser is preflighted. That is why a form post to a legacy
endpoint sails through while the JSON version of the same call fails: the JSON one requires an
OPTIONS handler the server never implemented, and it answers
405 Method Not Allowed or
404 to the preflight.
A preflight must be answered with Access-Control-Allow-Origin,
Access-Control-Allow-Methods covering your method, and
Access-Control-Allow-Headers covering every non-safelisted header you intend to send —
including Authorization, which is not safelisted and is the second most common cause
of a preflight failure after Content-Type. Access-Control-Max-Age lets the
browser cache the answer so the extra round trip is not paid on every call. For the full mechanism, the
CORS explained guide walks through each header in order.
Credentials add one more rule that catches people out. If you send cookies or use
credentials: 'include', the server may not answer with the wildcard
Access-Control-Allow-Origin: * — it must echo your exact origin and also send
Access-Control-Allow-Credentials: true. A configuration that works for anonymous requests
therefore breaks the moment authentication is added, which looks like an auth bug and is not. If you are
chasing a 401 in that situation, check the CORS response before the
token; API authentication covers the token side.
Fixing it, honestly
There is no front-end fix. CORS is a decision made by the server that owns the resource, and the browser is only enforcing it. Everything that genuinely resolves a CORS problem falls into one of three categories.
You control the API. Add the headers. Send
Access-Control-Allow-Origin with your specific origin rather than * — the
wildcard is fine for a genuinely public read-only API and wrong for anything else, and it is incompatible
with credentials. Handle OPTIONS on every route that accepts a preflighted method, answer it
with the allowed methods and headers, and set a sensible Access-Control-Max-Age. Add
Vary: Origin if you reflect the origin, or a cache will serve one origin's permissive
response to another.
You do not control the API. Route the call through a backend you do control. That backend is not a browser, so it is unaffected by CORS, and it can also keep the API key out of the client — which is usually a better reason to do it than CORS was. This is precisely the architecture our homepage tester uses, and it is worth understanding its cost: a server making arbitrary outbound requests on behalf of users is a server-side request forgery risk, which is why ours validates and pins every address before connecting.
Local development. Use your dev server's proxy configuration so the browser sees one origin. Disabling web security in a browser flag or an extension is not a fix: it changes nothing for your users, and it hides a problem that will reappear in production. If the plan is to ship a browser client against a third-party API, the CORS answer needs to be confirmed before the integration is designed around it — which is what this page is for. Compare notes with how browser-based clients work around CORS if you are choosing tooling.
Frequently asked questions
Why does a CORS failure give no error detail?
Because the detail would itself be a leak. If JavaScript could read why a request failed, a page could
distinguish a 403 from a 500 from a connection refused on a host you can reach and it cannot — which is
enough to map an internal network or probe an authenticated session. So the specification requires that a
blocked cross-origin request reject with a generic network error. In practice fetch rejects
with a TypeError whose message says nothing useful, and the real explanation appears only in
the browser console, which pages cannot read. This tool therefore reports what it can observe and tells
you where to look.
Which requests trigger an OPTIONS preflight?
Anything that is not a simple request. A request is simple when the method is GET,
HEAD or POST, every header you set is on the CORS-safelist
(Accept, Accept-Language, Content-Language,
Content-Type and a restricted Range), and Content-Type is one of
application/x-www-form-urlencoded, multipart/form-data or
text/plain. Sending application/json, adding any X- header, or
using PUT, PATCH or DELETE all force a preflight
OPTIONS request that must be answered before the real one is sent.
How is this different from the API Tester on the homepage?
It is the opposite design on purpose. The API Tester forwards your request through our server, which is not a browser and is therefore not bound by CORS at all — that is what lets it call any public endpoint. This page never proxies anything. The fetch is issued by your browser, from this origin, so the same-origin policy applies in full and you find out what a real front-end would experience. Use the API Tester to learn what the API returns; use this to learn whether your front-end can read it.
Why does my request work in curl but fail in the browser?
Because CORS is enforced by browsers, not by servers. curl, Postman, a server-side script and a mobile
app all ignore Access-Control-Allow-Origin entirely; the header only means something to a
browser deciding whether to hand a response to JavaScript. A 200 in curl and a blocked request in the
browser is not a contradiction — it is the normal situation for an API that has not opted in to
cross-origin access.
Can I fix CORS from the front end?
No. Every real fix is on the server that owns the resource: it must send
Access-Control-Allow-Origin, and for preflighted requests also
Access-Control-Allow-Methods and Access-Control-Allow-Headers. If you do not
control that server, the alternatives are a proxy you do control, or the vendor adding your origin.
Browser extensions that disable CORS change nothing for your users and hide the problem from you.
What is an opaque response?
It is what you get from fetch when the request mode is no-cors. The request
is sent and may well succeed, but the response is sealed: status reads as 0, headers are
empty, and the body cannot be read. It is only useful for fire-and-forget requests and for caching in a
service worker. Seeing an opaque response is not evidence that CORS is configured — it is evidence that
you asked the browser not to check.
Related tools and reference
API Tester
The proxied counterpart — bypasses CORS entirely so you can see the real response.
CORS Explained
Every Access-Control-* header, in the order the browser evaluates them.
cURL Converter
Build the non-browser command that shows you the headers CORS is hiding.
HTTP Headers
Safelisted, exposed and forbidden headers in context.
HTTP Status Codes
What a preflight answering 404 or 405 is telling you.
User Agent Parser
Confirm which browser and engine produced the behaviour you are seeing.