# API

## What it is

An API, short for application programming interface, is the published set of operations a
piece of software offers to other software: what each operation is called, what it accepts,
what it returns and how it reports trouble. The plural is APIs. The caller learns that surface
and nothing behind it, so the program on the far side can be rewritten, moved to another
machine or split in three, and the caller keeps working as long as the surface holds.

The word covers several layers. A library's API is the functions and types it exports, called
inside one process. An operating system's API is its set of system calls, and POSIX standardises
a common core of them so that a program written against `open` and `read` builds on more than
one Unix-like kernel. A web API is offered across a network, almost always over HTTP, and that
is what most people mean by the bare word today: a server answering requests at addresses such
as `/v1/orders`, usually with JSON in the body.

Web APIs come in a handful of styles. Resource-oriented ones, often called REST-style, give
each thing an address and use HTTP's methods to read and change it. RPC-style ones, such as
gRPC, publish named procedures with their arguments and results declared in advance. GraphQL
offers one endpoint and a type system, and the client writes a query naming exactly the fields
it wants back. Webhooks turn the direction round: the provider calls an address the customer
registered when something happens.

Three neighbouring words get mixed up with it. A protocol, such as HTTP, defines how messages
travel; an API is what one particular service says in that protocol. An SDK is a library in one
programming language that wraps someone's API, so a developer calls a function instead of
assembling requests. And the implementation is the code behind the API, which a caller never
needs to read.

## Why it matters

An API is the part of a system that other people build on, which makes it a promise that
outlives the code behind it. Rewriting a handler takes an afternoon. Changing what that handler
returns can break an app already installed on customers' phones, a partner's nightly import
or a script nobody remembers writing.

So the first decision is about audience. A private API, used only by the team's own frontend,
can change in the same pull request as its one client. A public API changes through
deprecation and notice, and every field it ships is a field to support for as long as anyone
reads it.

The second decision is how strict the contract is. A strict API declares a type and a limit for
every input, rejects what it does not recognise and answers each failure with a precise status
code. It catches mistakes at the door and is easier to document, test and generate clients
for. A lenient API accepts whatever arrives and does its best, which feels friendly in the first
month and then turns every accident of the implementation into behaviour somebody depends on.
Strictness costs design time up front; leniency costs more later, at a moment nobody chooses.

The third decision concerns other people's APIs. A payment provider, a mapping service or a
language model is an API the product calls and does not control: prices move, versions are
retired, quotas tighten. Calling a vendor directly from fifty places makes a switch a rewrite.
Calling it from one module the team owns makes the switch a change to that module. The wrapper
costs a little indirection and buys a way out.

Semantic Versioning gives this promise a vocabulary: the major number goes up when the public
API changes in a way that is incompatible, so a client can tell from the version alone whether
an upgrade is safe. Over HTTP there are several places to signal a break, and RFC 9205, the
IETF's guidance for protocols built on HTTP, lists a new link relation, a new media type or a
new header field among them.

## What it looks like in practice

The APIs behind several projects on this site were shaped by one question: what belongs in the
API, and what belongs somewhere else.

On a maritime platform for professional networking, company reviews and sponsored education,
[the Go API was kept deliberately thin](/portfolio/architected-a-layered-maritime-platform-separating-a-next-57/).
Built with Huma over Fiber, it routes, validates the request, applies the security filtering and
orchestrates, and holds no business logic at all. The rules live in PostgreSQL functions, so
a rule change happens in one layer, in SQL. The boundaries were written down and enforced in
review. The page calls the result a boring adapter, in the good sense.

A related project took the shaping out of the API as well.
[PostgreSQL functions assemble each response as JSON](/portfolio/designed-a-json-passthrough-architecture-where-postgresql-functions-58/),
and the Go handler receives it as `json.RawMessage` and forwards it untouched, through a small
`QueryJSON` helper. The DTOs, mappers and response structs a layered API usually collects fell
away, handlers got dramatically shorter, and the frontend no longer depended on Go.
The trade-off is worth naming: the shape of the API's responses is now defined in SQL, so
that is where a reviewer reads the contract.

An API that anyone can reach also has to defend itself. A public directory of companies and
professionals sits behind
[22 rate limiters, each built for the path it protects](/portfolio/built-fail-closed-abuse-controls-and-rate-limiting-100/),
with their state in Redis so a limit holds across instances. When Redis is unreachable the
limiters fail closed and refuse traffic. Cloudflare Turnstile, an origin lock, a bespoke
challenge on the directory and 351 lines of idempotency middleware, which stops a retried write
landing twice, complete the defences. The page states the cost plainly: a Redis problem becomes
a problem users can see, which was preferred to a Redis problem becoming a billing one.

## Where it goes wrong

API failures tend to arrive late, once the clients and the data have grown.

**A renamed field breaks a client nobody knew about.** Once an API is published, someone
depends on every observable detail: field names, the order of a list, the wording of an error.
Rename `customer_name` to `name` and a partner's integration breaks on the next request. Change
by addition instead: add the new field beside the old one, mark the old one deprecated, watch
the logs until nobody reads it, and remove it only in a new major version.

**A list endpoint gets slower every month.** It returns every matching row, which was fine with
fifty records at launch and times out at two hundred thousand. Paginate from the first release,
with a default page size and a hard maximum the client cannot exceed. For large or busy
collections prefer a cursor to an offset: with an offset, a row inserted between two requests
pushes another row onto the next page, where the client reads it twice.

```http
GET /v1/orders?limit=50&after=ord_8f2c HTTP/1.1
Host: api.example.com
```

**One customer reads another customer's order.** The API checks that the caller is signed in
and then loads `/v1/orders/42` by its identifier alone, so changing the number in the address
walks through other people's data. Authentication answered who is calling; nothing asked
whether that caller owns order 42. Scope every lookup by owner inside the query itself, and
keep a test that signs in as one account and requests another account's records.

**The response carries columns nobody meant to publish.** An endpoint serialises a database row
or an ORM object as it stands, so a password hash, an internal flag or a supplier's cost price
goes out to every client, and a column added next year is published the day it is created. The
same mistake on input lets a request set `role` or `is_admin` by including it in the body.
Give each endpoint explicit input and output types that list the fields allowed, so publishing
a field becomes a decision someone makes.

## Further reading

- [RFC 9110: HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110) — the methods, status
  codes and headers that every web API is built from, defined by the IETF.
- [RFC 9205: Building Protocols with HTTP](https://www.rfc-editor.org/rfc/rfc9205) — best
  current practice for designing an application on top of HTTP, including how to evolve it.
- [RFC 9457: Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc9457) — a
  standard JSON shape for error responses, so clients parse every failure the same way.
- [RFC 6585: Additional HTTP Status Codes](https://www.rfc-editor.org/rfc/rfc6585) — where 429
  Too Many Requests and its `Retry-After` hint for rate-limited clients are defined.
- [Semantic Versioning 2.0.0](https://semver.org/) — the rules for signalling an incompatible
  API change through a version number.

<https://engineer.company/glossary/api/>
