Your AI coding agent built a frontend on localhost:3000 and a separate API on localhost:8000. The request works in curl and in Postman. In the browser the console shows a CORS error, the response is blocked, and your code only sees a generic failed fetch.
The instinct is to change the fetch call. The fix is almost always on the server.
Why this happens
CORS is enforced by the browser, not by your API. MDN defines it as an HTTP-header based mechanism that lets a server indicate origins other than its own from which a browser should permit loading resources. An origin is the scheme, domain and port together, so http://localhost:3000 and http://localhost:8000 are different origins. That is why curl never complains: it is not a browser and does not check.
Two details trip up agent-written code:
- Your code cannot see the reason. MDN notes that for security, specifics about a CORS error are not available to JavaScript. The only place to read what went wrong is the browser console.
- JSON requests get a preflight. Only a short list of requests skip it. MDN’s list of what a “simple request” may use for
Content-Typeisapplication/x-www-form-urlencoded,multipart/form-dataandtext/plain. AfetchwithContent-Type: application/json, or with anAuthorizationheader, falls outside that, so the browser first sends anOPTIONSrequest and only continues if the server answers it correctly. Agents often add a handler forGETandPOSTand forget thatOPTIONSexists.
How to tell if this is your problem
- Open the console and the Network tab. Look for the blocked request. If you see an
OPTIONSrequest to the same URL before it, the preflight is involved. - Check the response headers on that
OPTIONSrequest. IfAccess-Control-Allow-Originis missing, the server never opted in. - Check whether you send cookies. If your fetch uses
credentials: "include"and the server sendsAccess-Control-Allow-Origin: *, the browser blocks it. MDN says the server must not use the wildcard for a credentialed request and must name an explicit origin.
The fix
Make the API answer the preflight and name your frontend’s origin. The headers MDN’s example uses for a preflight response look like this:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Access-Control-Allow-Originis the exact origin of your frontend.Access-Control-Allow-Methodslists the methods the real request may use.Access-Control-Allow-Headerslists the request headers you send, such asContent-TypeandAuthorization.Access-Control-Max-Agecontrols how long the browser caches the preflight result. MDN says the default is 5 seconds, and each browser caps the value.
Most frameworks do this for you through a CORS middleware (Express has the cors package, FastAPI has CORSMiddleware). Configure it with your frontend origin rather than letting the agent paste in a wildcard.
If you send cookies
Use credentials: "include" in fetch, and have the server send Access-Control-Allow-Credentials: true together with an explicit origin, not *. MDN says that without that header the browser rejects the response and does not hand it to your code. The preflight response needs the same header, because preflight requests never carry credentials themselves.
If the origin changes per request
If your server picks the origin from an allowlist, MDN says it should also send Vary: Origin, so caches know the response differs by the Origin request header.
Do not “fix” it with a wildcard in production
Access-Control-Allow-Origin: * makes the error disappear for public, cookie-less APIs. It is the wrong answer for anything that uses cookies, and it is the shortcut agents reach for first. Allow the specific origins you own.
How to avoid this next time
Tell the agent up front: “The frontend and API run on different origins. Add CORS middleware on the API that allows only these origins: <your frontend URLs>. Handle OPTIONS preflight, allow the Content-Type and Authorization headers, and never use * when cookies are involved.” In review, open the Network tab, find the OPTIONS request and confirm the response carries the three Access-Control-Allow-* headers.
Comments
Sign in to join the conversation.
No comments yet — be the first.