CORS Tester

This page issues a real cross-origin request from your own browser — nothing is proxied — and reports what the browser actually did with it: handed you the response, sealed it as opaque, or blocked it outright. That is the whole point: it reproduces exactly what your front-end code would experience.

The request leaves your machine, from the origin https://api-tester.com, and is subject to the same-origin policy in full. It is never forwarded through our server. Do not put a production credential in the headers box — a real request will carry it to the target.

Try:

No request sent yet.

The outcome will appear here.

Preflight predictor

Before the browser sends a non-simple cross-origin request it sends an OPTIONS preflight and waits for permission. This predicts whether a given combination triggers one — no network request involved.

Choose a method and press Predict.

The prediction will appear here.

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

OutcomeWhat happenedWhat 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, HEAD or POST.
  • Every header your code sets is on the CORS-safelist: Accept, Accept-Language, Content-Language, Content-Type, and Range with a simple byte range. Headers the browser sets for itself do not count.
  • Content-Type, if present, is one of application/x-www-form-urlencoded, multipart/form-data or text/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