The distinction that quietly changes how you design, estimate, and ship software Link to heading
I recently had one of those realisations where everything suddenly clicks, and you are annoyed it took this long.
The realisation was this:
Not all APIs are meant for browsers.
We use the same protocol, the same tools, and often the same words. But we are talking about two very different things. Failing to name that difference is where a lot of unnecessary complexity comes from.
The problem is partly language Link to heading
The words we choose here are not just descriptive. They shape design decisions, expectations, and ultimately the systems we build.
We casually say things like:
- “We have a Web API”
- “The frontend will call the API”
- “We just need to enable CORS”
Those phrases quietly smuggle in an assumption:
A browser is just another HTTP client.
It is not.
A browser is a hostile execution environment with its own security rules, history, and constraints. For example, it will automatically attach cookies to cross-site requests. If JavaScript can run, the browser assumes the worst and enforces rules to protect the user.
When we blur the words, we blur the responsibility.
Two different kinds of API Link to heading
HTTP APIs Link to heading
These are classic machine-facing APIs, not designed to be called directly from browsers.
They are called by:
- Backend services
- Mobile apps
- Partner systems
- CLI tools
- Scheduled jobs
Typical characteristics:
- Explicit authentication (tokens, API keys, mTLS)
- No cookies
- No CORS
- No CSRF
- Often no
OPTIONSsupport at all, which only becomes a problem once a browser enters the picture.
They are intentionally boring by design.
That boredom is a feature, not a limitation.
Web APIs Link to heading
A Web API is an API that can be safely called by JavaScript running in a browser.
They are far rarer than people think.
That single requirement changes everything.
Characteristics:
- Browsers enforce Same Origin Policy
- CORS must be designed and configured
- Cookies often exist
- CSRF must be handled
- Preflight requests happen
- The browser may block responses even when the server replied correctly
This is not “just HTTP”.
This is HTTP plus decades of security scar tissue.
The mistake most teams make Link to heading
We build an HTTP API.
Later, often under time pressure, we say:
“Let’s just point the browser at it.”
That sentence hides real work.
I learned this the hard way a few clients ago.
As part of a new product push, I was told that our existing “Web API” was going to start being called directly from the browser to support a new user journey. I did not think much of it at first. After all, it was already an API.
Then the bug reports started coming in.
They mentioned CORS errors and 405 Method Not Allowed responses.
The CORS part clicked fairly quickly for me. Solving it was going to be harder, but at least I understood the problem.
The 405 errors are what really stopped me.
“What is this OPTIONS verb?”
I am ashamed to admit that, after more than ten years of software development and many years working on web systems, I had never had to think about it. Our API quite reasonably did not support OPTIONS requests. It never needed to.
That was the moment the penny dropped.
“Our API did not support OPTIONS requests. It never needed to. Until we pointed a browser at it.”
We were not dealing with a misconfigured API.
We were trying to turn an HTTP API into a Web API without acknowledging the cost.
In the end, we bailed on the idea entirely. We introduced a proper BFF to sit between the browser and the existing HTTP API, and the problems disappeared.
Suddenly you are dealing with:
- CORS rules and preflights
- Credential modes
- CSRF tokens
- Cookie scope and SameSite flags
- Failure modes that only exist in browsers
None of this is free.
It was simply not named early enough to be estimated.
Naming things correctly changes behaviour Link to heading
Here is the rule I now try to enforce:
- Call it a Web API only if a browser can safely call it
- Call it an HTTP API if it cannot
Most APIs are HTTP APIs in practice.
Very few should be Web APIs.
Once you adopt this language, design decisions become clearer very quickly:
- Do we need cookies at all?
- Do we want to support browsers here?
- Are we prepared to handle CORS and CSRF properly?
If the answer is no, do not pretend otherwise.
Why this matters for estimation and delivery Link to heading
This distinction often first surfaces not in code, but in product planning conversations, roadmap discussions, or a casual “can the frontend just call this?” question.
“Expose this API to the browser” is not a small change.
It means:
- New security decisions
- New infrastructure concerns
- New testing requirements
- New ways things can break
When teams fail to name this shift, it shows up later as missed estimates, rushed security work, or brittle designs.
When browsers get involved Link to heading
In practice, many mature systems converge on something like this:
- The browser talks to a BFF or Web API
- That boundary handles cookies, CORS, and CSRF
- Core services expose HTTP APIs only
- Internal calls stay simple and fast
Where it makes sense:
- BFF to backend communication can use gRPC
The key idea is containment. Browser complexity lives at the edge, instead of leaking into the rest of the system.
This is simply a starting point. Not a rule, and not something to apply blindly.
The takeaway Link to heading
Nothing about HTTP itself changed.
What changed was the threat model.
The moment a browser gets involved, you opt into a very different set of rules.
If JavaScript in a browser can call an API, treat it as a Web API and design it deliberately.
If it cannot, keep it as an HTTP API and protect that simplicity.
Name the distinction early.
It will save you security bugs, bad estimates, and architectural regret.