When testing an API endpoint in Postman or with curl, the request may return a successful response with expected data. However, running the same request from client JavaScript inside a web browser can fail.

This difference often leads to confusion because the server appears to be functioning normally. The difference occurs because Postman and web browsers handle cross-origin HTTP requests under different security rules.

Why Postman succeeds while the browser fails

Postman can send an HTTP request and show the response it receives without applying browser CORS enforcement in the same way as browser JavaScript.

A web browser operates under a different model. Web browsers execute scripts inside an application context and enforce the Same-Origin Policy. When client JavaScript initiates a cross-origin request, the browser checks whether the server explicitly allows that interaction.

A successful response in Postman demonstrates that the server is reachable and able to answer that specific client request. It does not prove that the server provides the CORS response headers required by a web browser.

What the browser actually checks

The browser evaluates cross-origin communication using the Same-Origin Policy and CORS response headers.

An origin is defined by three components:

  1. The scheme (such as HTTP or HTTPS)
  2. The host (such as app.example.test or api.example.test)
  3. The port (such as 443 or 80)

If any of these three components differ between the web page and the API, the request is cross-origin.

For cross-origin requests, the browser requires the server to provide specific HTTP response headers:

  • Access-Control-Allow-Origin: Indicates which requesting origin is permitted to read the response.
  • Access-Control-Allow-Headers: Indicates which request headers are permitted when a preflight check applies.
  • Access-Control-Allow-Methods: Indicates which HTTP methods are permitted for cross-origin requests.
  • Access-Control-Expose-Headers: Specifies which non-safelisted response headers can be made available to client JavaScript.

When credentials are included in a cross-origin request, the server cannot use a wildcard for Access-Control-Allow-Origin. It must specify the explicit requesting origin.

A minimal preflight example

Before sending certain cross-origin requests, the browser may send an automatic OPTIONS preflight request. The preflight asks the server whether the intended method and headers are permitted.

If the server response to the OPTIONS preflight does not match what the browser requires, the browser stops the request flow before the main request proceeds.

HTTP exchange

Preflight Origin Mismatch

broken

Request

OPTIONS /v1/profile HTTP/1.1
Host: api.example.test
Origin: https://app.example.test
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type

Response

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://other-app.test

The allowed origin in the preflight response does not match the requesting browser origin. The browser does not permit the intended cross-origin request flow.

Response Header Not Exposed to JavaScript

unexpected

Response

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.test
Content-Type: application/json
X-Request-ID: req_example_123

The response can be shared with browser JavaScript, but X-Request-ID is a custom non-safelisted response header that browser JavaScript cannot read unless the server exposes it through Access-Control-Expose-Headers.

Common causes of browser CORS failures

Missing or Mismatched Access-Control-Allow-Origin

The API server omitted the Access-Control-Allow-Origin header or returned an origin value that does not match the requesting browser page origin. While general HTTP clients display the response, the browser blocks client JavaScript from accessing it.

Preflight Response Does Not Permit the Intended Request Flow

A browser may send an OPTIONS preflight request before certain cross-origin requests. If the preflight response does not permit the intended method or request headers, the browser can stop the intended request flow.

Credentialed Cross-Origin Response Uses Wildcard

The request included credentials, but the server returned Access-Control-Allow-Origin: * instead of specifying the explicit requesting origin, causing the browser to reject the response.

Custom Response Header Is Not Exposed to Browser JavaScript

The HTTP request succeeded with status 200 OK, but client-side JavaScript cannot read custom non-safelisted headers because the server omitted them from Access-Control-Expose-Headers.

CORS is not the same as a 401, 403, or 500 response

An HTTP status code such as 401 Unauthorized, 403 Forbidden, 404 Not Found, or 500 Internal Server Error represents a genuine outcome generated by the server. When Postman displays one of these error codes, it confirms that the server received the client request and returned that specific status.

However, receiving an HTTP error does not mean CORS is irrelevant. When a browser initiates a cross-origin request that encounters an authentication failure or server exception, browser JavaScript still cannot read the error payload unless the server includes valid CORS response headers.

If an error response does not include the CORS headers required for the browser request, browser JavaScript may not be able to read that error response normally. Inspect the Network panel to review the request flow, HTTP status, and response headers.

What to compare before changing code

Before modifying frontend logic or adjusting server authentication settings, compare the exact request and response headers across environments:

  1. Check the requesting origin: Identify the exact scheme, host, and port sent in the browser’s Origin header.
  2. Confirm preflight requirement: Determine whether the browser sent an automatic OPTIONS preflight request for non-simple methods or headers.
  3. Review Access-Control-Allow-Origin: Verify that the server returns an allowed origin matching the browser origin, without wildcards on credentialed requests.
  4. Inspect allowed headers and methods: Confirm that custom request headers (like Authorization) and HTTP methods are explicitly allowed in the preflight response.
  5. Verify exposed headers: Check whether non-safelisted response headers need to be listed in Access-Control-Expose-Headers for JavaScript access.

How to verify the fix

Use curl to inspect server response headers under a simulated origin, and then confirm behavior in the browser DevTools Network panel:

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

Verify whether the response returns the expected Access-Control-Allow-Origin header for the origin and whether Access-Control-Allow-Headers permits the requested headers.

Key takeaway

Postman succeeds because it does not enforce the browser’s Same-Origin Policy. To fix browser-specific failures, configure the origin server to return the necessary Access-Control-Allow-* headers matching the client’s origin, HTTP method, and request headers.