When debugging a cross-origin API call that fails in a web browser, developers frequently look at the intended POST or PUT request. However, the root cause is often that the preliminary OPTIONS preflight request failed before the main request was ever sent.

How the OPTIONS preflight fits into the request flow

In standard browser operation for non-simple cross-origin requests:

  1. Step 1 (Preflight): The browser automatically issues an OPTIONS request carrying Origin, Access-Control-Request-Method, and Access-Control-Request-Headers.
  2. Step 2 (Evaluation): The browser evaluates the server’s HTTP status code and Access-Control-Allow-* response headers.
  3. Step 3 (Actual Request): If and only if the preflight passes, the browser sends the actual POST, PUT, or DELETE request with the payload.

If Step 2 fails, Step 3 never occurs. The actual request is canceled by the browser client.

Missing or mismatched CORS response headers

Even if the server returns a successful status code like 200 OK or 204 No Content to the OPTIONS request, the preflight will still fail if required CORS headers are missing or mismatched:

  • Missing Access-Control-Allow-Origin: The preflight response must specify the requesting origin.
  • Missing or incomplete Access-Control-Allow-Methods: If the client intends to send a PUT request, the header must include PUT.
  • Missing or incomplete Access-Control-Allow-Headers: If the client sends Authorization or Content-Type: application/json, those header names must be explicitly enumerated in Access-Control-Allow-Headers.

OPTIONS endpoint or route behavior

A preflight failure often happens because the server backend router is not configured to respond to the OPTIONS HTTP method:

  • Some web application routers and API frameworks match routes strictly by HTTP method. If a route defines only POST /v1/items, an incoming OPTIONS /v1/items request may trigger an automatic 404 Not Found or 405 Method Not Allowed.
  • To fix this, CORS middleware must handle OPTIONS requests globally across all API routes before route matching occurs.

HTTP 401, 403, 404, and 405 during preflight

Preflight requests frequently encounter standard HTTP error status codes for specific structural reasons:

401 Unauthorized and 403 Forbidden

The browser intentionally does not send credentials (such as Authorization headers or cookies) with an OPTIONS preflight request. If the backend server places an authentication middleware before its CORS middleware, the unauthenticated OPTIONS request is rejected with 401 Unauthorized or 403 Forbidden. The browser treats this as a preflight failure.

404 Not Found

The router or gateway does not have a route defined for OPTIONS on that specific path.

405 Method Not Allowed

The server recognizes the resource path but rejects the OPTIONS method.

CORS failure versus ordinary API error

It is essential to distinguish between a CORS preflight failure and an error returned by your actual API business logic:

  • Preflight Failure: The OPTIONS request failed or returned invalid CORS headers. The actual request was never transmitted to the server. No database changes or server-side side effects occurred.
  • Ordinary API Error: The preflight succeeded (or was not required), and the actual POST request was executed by the server. The server then returned 400 Bad Request, 401 Unauthorized, or 500 Internal Server Error with proper Access-Control-Allow-Origin headers. In this case, your frontend JavaScript can read the error response body directly.

What to inspect in DevTools

When diagnosing a preflight error:

  1. Open Browser DevTools and select the Network tab.
  2. Filter by Fetch/XHR or All.
  3. Look for the request with the OPTIONS method.
  4. Check the Status Code of the OPTIONS line:
    • If it is 401 or 403, verify middleware ordering on the server.
    • If it is 404 or 405, verify router and gateway handling for OPTIONS.
    • If it is 200 or 204, inspect the Response Headers to confirm Access-Control-Allow-* values.

Common causes

Authentication Middleware Intercepts OPTIONS Requests

Backend authentication filters or API gateways require auth tokens on all requests, rejecting unauthenticated OPTIONS preflights with 401 Unauthorized or 403 Forbidden.

Router or Endpoint Rejects OPTIONS with 404 or 405

The web application routing layer explicitly maps only GET or POST handlers, rejecting OPTIONS preflights with 404 Not Found or 405 Method Not Allowed.

Preflight Response Omits Required Access-Control Headers

The server responds with 200 or 204 but omits Access-Control-Allow-Methods or Access-Control-Allow-Headers covering the requested method and headers.

Reverse Proxy or Ingress Gateway Drops Preflight Requests

An intermediate edge proxy, load balancer, or web application firewall (WAF) blocks OPTIONS methods or fails to return CORS response headers.

What to check before changing code

  1. Inspect status and headers in DevTools: Check the Network tab for the preliminary OPTIONS request. Determine whether it returned an error status code (401, 403, 404, 405) or succeeded without required CORS headers.
  2. Review server middleware order: Ensure that CORS middleware runs before any authentication guards or route-specific handlers.
  3. Verify method and header lists: Ensure the server’s Access-Control-Allow-Methods and Access-Control-Allow-Headers match what the browser client is requesting.

How to verify the fix

Simulate the browser’s preflight request with curl:

curl -i -X OPTIONS https://api.example.test/v1/items \
  -H "Origin: https://app.example.test" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: authorization, content-type"

Verify that the response returns 200 OK or 204 No Content, includes Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers, and contains no auth error body.

Key takeaway

A failing preflight request prevents the browser from sending your actual API request. Look directly at the OPTIONS request in DevTools Network to determine whether the failure was caused by auth middleware interception (401/403), unhandled routing (404/405), or missing Access-Control-Allow-* response headers on the backend.