What an API Actually Is (You've Been Using One All Day)
It's a contract, not a technology, and confusing the two causes most of the pain

Contents
Somebody at a family gathering once asked me what an API was, expecting a technical answer, and the thing that actually landed was comparing it to a restaurant menu: you don’t need to know how the kitchen works, you just need to know what you’re allowed to order and what you’ll get back. Every weather app, every “sign in with” button, every smart plug you control from your phone is making a request across that same kind of boundary — a defined contract between two pieces of software that don’t need to know anything about each other’s internals, only what’s promised at the interface between them. Once that framing clicks, “API” stops sounding like a piece of technology and starts being recognisable as a much older idea — a contract — implemented in whatever technology happens to be convenient.
The Contract, Not the Wire Format
An API — application programming interface — is a specification of what one piece of software will accept from another, and what it promises to return, without either side needing to know how the other is actually implemented. This is a much older and more general idea than “a web endpoint that returns JSON,” which is only the specific, currently fashionable implementation most people picture, and conflating the general idea with that one implementation causes real confusion, because plenty of APIs predate the web entirely — an operating system’s system calls are an API, a programming language’s standard library is an API, a database driver’s function signatures are an API, all of them contracts specifying what you can call and what you’ll get back, none of them involving HTTP or JSON at all.
The web API — REST, GraphQL, whatever transport and format a given service chose — is simply the specific flavour that dominates today because HTTP is universally supported, text formats like JSON are easy to read and debug, and the web’s infrastructure (load balancers, caching, browsers) already understands how to move that kind of traffic around reliably at scale. But the underlying idea is identical to the operating system case: a request comes in, following an agreed shape, and a response goes out, following another agreed shape, with everything in between hidden and irrelevant to the caller.
| |
The caller here doesn’t know or care whether the server is querying a weather database, calling out to a third-party provider, or running a model that predicts the answer — the contract only promises “send a city, get back a temperature and conditions in this shape,” and everything behind that promise is the server’s business entirely. That hiding of implementation behind a stable interface is the actual point of an API, not the specific choice of JSON over HTTP, and it’s why a well-designed API can completely rewrite its internals — swap databases, change providers, rearchitect the whole backend — without breaking a single client, provided the contract itself doesn’t change.
Why Versioning Exists, and Why Breaking It Is So Costly
Because callers depend on the shape of a response staying stable, changing that shape is one of the more dangerous things an API provider can do, and it’s why mature APIs version themselves explicitly — that /v1/ in the URL above isn’t decoration, it’s a promise that this specific contract won’t change underneath existing callers, and any breaking change gets shipped as /v2/ instead, running alongside /v1/ for as long as anyone still depends on the old shape. Removing a field, renaming one, or changing a data type from a number to a string are all breaking changes from the caller’s point of view, even if they look like minor tidy-ups from the provider’s side, because code written against the old contract will fail in ways that range from an obvious crash to a subtle silent misinterpretation, depending entirely on how defensively the calling code was written.
This is the entire reason API providers agonise over backward compatibility in a way that looks, from outside, like excessive caution — adding a new optional field is safe, because existing callers simply ignore fields they don’t recognise; removing or renaming an existing field is not safe, because some caller, somewhere, almost certainly depends on it being there in exactly that shape, and API providers generally have no visibility into every caller depending on them, especially for anything public. The discipline of “additive changes only, breaking changes get a new version” exists specifically because the alternative is breaking software you don’t control and often can’t even see.
Authentication Is a Separate Layer From the Contract
The API contract describes what a request and response look like; it says nothing by itself about who’s allowed to make the request, which is a deliberately separate concern handled by an authentication layer sitting in front of or alongside the contract. That Authorization: Bearer header in the example above is doing exactly this job — proving to the server that the caller has permission to use the API at all — and it’s worth understanding as genuinely distinct from the API’s data contract, because the two fail in different ways and get fixed differently. A 401 or 403 response means the identity or permission layer rejected the request before the actual API logic ever ran; a 400 or a malformed response means the request reached the logic but didn’t match the expected contract shape. Debugging one as if it were the other wastes real time, and it’s an extremely common mistake for anyone newer to working with third-party APIs. For more on how that authorisation piece actually works underneath, running a self-hosted SSO for everything in the rack covers exactly this layer in depth, and it’s a genuinely separate topic from the request/response contract this piece is about.
Rate Limits Are Part of the Contract Too, Even When Undocumented
A less obvious part of most API contracts is an implicit or explicit promise about how much traffic the provider will actually accept from you, and hitting that ceiling produces a specific, recognisable failure — usually a 429 status code, sometimes accompanied by a Retry-After header telling you exactly how long to wait before trying again. Rate limits exist because an API is a shared resource sitting behind a server with finite capacity, and without some cap, one caller’s runaway retry loop can degrade service for every other caller hitting the same backend. Treating a 429 as a signal to back off and retry later, with actual exponential delay between attempts, is the correct response; treating it as an error to immediately retry against is how a single misbehaving client turns a normal rate limit into a self-inflicted outage, hammering the same endpoint harder exactly when it’s already telling you to slow down.
| |
Idempotency: the Part of the Contract That Saves You From Double-Charging Someone
One more part of a well-designed API contract deserves its own explanation, because getting it wrong has caused some genuinely expensive real-world incidents: idempotency, meaning whether making the same request twice produces the same result as making it once, or whether it duplicates the effect. A GET request is naturally idempotent — asking for the weather twice doesn’t create two weathers — but a POST that charges a card or places an order is not naturally idempotent at all, and this matters enormously the moment networks get involved, because networks are unreliable in a very specific, very annoying way: a request can time out on the client side after the server already processed it successfully, leaving the client with no way to tell “it failed” apart from “it succeeded, but the confirmation got lost in transit.”
Naively retrying a timed-out request in that situation risks charging the customer twice, once for the original request that actually did succeed silently, and once for the retry the client sent believing the first attempt had failed. Payment APIs solve this with an idempotency key — a unique identifier the client generates once per logical operation and includes on every retry of that same operation, letting the server recognise “I’ve already processed this exact key” and return the original result instead of repeating the side effect.
| |
Retry that exact request with the same key after a timeout, and a well-built API returns the original charge result rather than creating a second charge — the key is the mechanism that turns an inherently unsafe “retry a payment” operation into a safe one. This is worth checking for specifically before building any retry logic against a third-party API that has side effects, because assuming an API is safe to blindly retry, when it hasn’t documented idempotency support, is exactly how a flaky network connection turns into a duplicate order or a doubled invoice, discovered only when a customer complains days later.
Troubleshooting: Reading an API Failure Correctly
Start by separating the failure into layers, in this order: did the request reach the server at all (a network or DNS problem, nothing to do with the API contract itself), did the server accept the caller’s identity (a 401/403, an authentication problem), did the server accept the request’s shape (a 400, a contract mismatch — check the API’s current documented version against what your code is actually sending), and only then look at whether the response you got back matches what you expected in content, not just in status code. Most “the API is broken” reports I’ve debugged turn out to be one of the first three layers, caught and misdiagnosed as the fourth. If you’re building against a third-party API and it changes behaviour with no announcement, check for a version header or URL segment first — providers that care about compatibility almost always give you an escape hatch to pin to an older version rather than being forced to chase a moving target.
This is the same reason well-designed webhook systems — an API calling you, rather than the other way round — include a unique event ID with every delivery and expect the receiving side to deduplicate on it, since the sender genuinely cannot guarantee exactly-once delivery over an unreliable network and will occasionally send the same event twice rather than risk silently dropping it once. Treating “at least once, deduplicate yourself” as the honest default assumption, rather than trusting any API to deliver exactly once, is the difference between a webhook handler that’s robust under real network conditions and one that quietly double-processes an event during the next transient blip.
Is It Worth Understanding This?
Yes, and it’s a genuinely small idea once you strip the buzzword away — a contract, versioned so it doesn’t break existing callers, sitting behind an authentication layer that’s a separate concern from the contract itself, with rate limits as an implicit part of the deal. None of that requires knowing a specific framework or language; it requires recognising the same shape everywhere it shows up, from a REST endpoint to an operating system syscall. If this reframing was useful, a JWT is a cookie with a superiority complex covers a specific, extremely common piece of that authentication layer in more depth, and what DNS actually does when you type a URL is the layer underneath all of this that gets your request to the right server in the first place.




