An API Built to a Contract, With an End Date
This is one of the few things here we own end to end rather than staffing. A defined set of endpoints, an agreed auth model, documentation that matches the code, and a price fixed before anybody starts.

An API Is a Promise That Other People Build On
A user interface can be redesigned on a Tuesday and nobody outside the company notices. An API cannot. The moment a mobile app, a partner integration or another team depends on a field, the shape of your responses becomes a commitment, and the cost of getting it wrong is paid by everyone downstream for years.
That is why this is fixed-scope work rather than something to improvise while doing other things. We agree the resources, the auth model, the error shapes and the pagination behaviour in writing first, build against that, and hand over documentation that matches what the code actually does.
If instead you want an API built incrementally alongside a changing product, that is a job for a Node developer on your team, not a fixed-price project. Fixed scope and moving requirements produce change requests, and nobody enjoys those.
What the Project Covers, and What It Does Not
Both lists are written into the agreement before work starts, which is the entire point of buying it this way.
Delivered as part of the project
- The agreed resources and endpoints, with request and response schemas
- Authentication and per-record authorisation, specified rather than assumed
- Pagination, filtering and sorting behaviour, including the awkward edges
- A consistent error format and a documented list of what each code means
- An OpenAPI document that is checked against the implementation
- A test suite that exercises the contract, not only the happy path
A separate phase, quoted separately
- Resources nobody mentioned until the build was underway
- Client applications that consume the API, web or mobile
- Rebuilding a data model that turns out to be the real problem
- Ongoing operation, on-call cover and incident response afterwards
- Cloud and infrastructure costs, which are billed to you directly
- Indefinite changes of direction, which need a developer rather than a project
REST or GraphQL, Decided on Evidence
Both are reasonable. The choice is about how many differently shaped clients you have and how much operational machinery you want to own.
The default, and usually the right one
Resources at predictable paths, verbs that mean what they say, and status codes that intermediaries already understand. Caching, rate limiting, logging and monitoring all work with no extra effort because every tool in the stack already speaks HTTP.
- Every developer you will ever hire already knows it
- A slow endpoint is trivially visible in any access log
- Its weakness is real: clients fetch too much, or make three calls for one screen
Worth it when clients genuinely differ
One endpoint, a typed schema, and clients asking for exactly the fields they need. That is a real advantage when several consumers want different slices of the same graph and you would otherwise be adding an endpoint per screen forever.
- Caching moves from free to a design problem you have to solve yourself
- Rate limiting by request count is meaningless when one query can be enormous
- Authorisation has to be enforced per field and per node, not per route
The honest heuristic: if you cannot name two consumers who want materially different shapes of the same data, GraphQL is buying you flexibility you will not use and handing you operational work you did not have. Choosing it because it is more modern is how teams end up rate limiting by query complexity eighteen months later.
What Every Request Passes Through
Each layer has one job and fails in its own way. Most of the design work in an API project is deciding what belongs at which step.
Where each layer goes wrong
Rate limiting at the edge is the cheapest protection you will ever add and the one most often skipped until an integration partner writes a retry loop without a delay. Authentication answers who is calling and nothing more, which is why treating it as the whole security story is the most common serious flaw we find in an audit.
Validation before authorisation keeps malformed input out of code that assumes it is well formed, and it belongs in one place with a schema rather than scattered through handlers. Authorisation is the step that has to ask whether this caller may see this particular record; an endpoint that checks only that somebody is logged in will happily hand over another customer’s data when the identifier in the path is changed.
The handler should be boring by the time a request reaches it. If it is still deciding permissions and parsing input, the layers above are not doing their jobs. The data layer is where the slow query lives, and it is worth knowing which endpoints issue one query per item in a list before a customer discovers it for you.
The Six Things Settled Before Any Code Is Written
These are the decisions that are expensive to reverse once somebody is depending on them.
Resource modelling
What the nouns are, how they relate, and which of them clients actually address. The usual mistake is exposing database tables directly, which welds your storage decisions to your public contract and makes both harder to change.
The other usual mistake is the opposite: an abstraction so general that every caller needs three requests and a diagram to do anything simple.
Versioning strategy
Decide before launch how a breaking change reaches consumers. A version in the path is blunt and obvious and works. A strict policy of only adding fields, never removing or repurposing them, is cleaner and requires discipline that outlasts the people who agreed to it.
Either is fine. Having no policy and discovering the need for one during an incident is not.
Authentication and authorisation
Short-lived access tokens with refresh for first-party clients; narrowly scoped credentials for service-to-service traffic. The half that matters more is authorisation, enforced per record rather than per route, and tested as deliberately as any feature.
If you already run an identity provider, we integrate with it rather than building a second login system for you to maintain.
Pagination and filtering
Every collection endpoint is paginated from the first day, including the ones that currently return eleven rows. Cursor pagination is stable when records are being inserted underneath the reader; offset pagination is simpler and silently skips or repeats rows on a busy table.
Filtering and sorting get an explicit allowed list. An open-ended filter syntax is a promise to support every future query shape forever, and an invitation to a table scan.
Rate limiting and abuse
Limits per credential rather than per address, because addresses are shared and credentials are not. Return the limit, what is left and when it resets in headers, so a competent client can back off instead of hammering and guessing.
Write down what happens when a legitimate partner exceeds a limit at three in the morning, because the answer decides whether it becomes an incident or an email.
Error shapes clients can handle
One consistent body for every failure: a stable machine-readable code, a human-readable message, and field-level detail for validation problems. Clients should never have to match on the text of a message, because that text will be edited by somebody who does not know it is load bearing.
Status codes that mean what they say, and a correlation identifier in the response so a support conversation can find the exact request in the logs.
Documentation and Tests Are the Deliverable Too
An API that only its author can use has not been delivered, whatever the endpoints do.
OpenAPI that cannot drift
The specification is generated from the code or verified against it in the test suite, so a response that stops matching its schema fails the build rather than confusing somebody in six months. Hand-maintained documentation is worse than none, because people believe it and build against it.
Alongside the generated document go the things a schema cannot express: what each error code means, the rate limits, how pagination behaves at the boundaries, and a worked example of the two or three flows most consumers implement first.
Tests against the contract
Integration tests that call the API the way a client would, against a real database rather than a set of mocks that agree with whatever the code currently does. The interesting cases are the unhappy ones: expired tokens, a caller reaching for somebody else’s record, a filter with nonsense in it, the second page of a list that changed underneath.
More on how we approach this at testing and automation. If the project is a rescue rather than a new build, the first phase is usually enough tests to change anything safely.
Everything is written in your repository, in your style, using the frameworks you already run — Express, NestJS or Fastify. We do not introduce a framework your team would then have to learn in order to maintain what we left behind.
Fixing an API You Already Have
More common than a new build, and usually scoped after reading the code rather than from a description of it.
API Project Questions
Should we use REST or GraphQL?
REST unless you can name the specific reason not to. GraphQL earns its place when several different clients need different shapes of the same data and you are tired of building an endpoint per screen. The price is that caching, rate limiting and authorisation stop being solved by the HTTP layer and become your problem. If one web client and one mobile client consume the API, a handful of well-chosen REST endpoints is less work to build and much less work to operate.
Do we need versioning from day one?
You need a versioning decision from day one, which is not the same as a version number. Decide how a breaking change will reach consumers and write it down. While every consumer ships with you, you can change the contract and coordinate the deploy. The moment somebody you do not control depends on a field, you need either a version in the path or a strict policy of only ever adding, and that policy is far more discipline than it sounds.
What does a fixed-scope API project include?
The endpoints in the agreed contract, the authentication and authorisation model, pagination and filtering behaviour, the error shapes, an OpenAPI document that matches the implementation, and a test suite that runs against the contract. What it excludes is anything discovered later that was not in the contract. That becomes a second phase with its own price rather than an argument about what was implied.
Can you fix an existing API rather than build a new one?
Often that is the better project. The usual work is normalising inconsistent error responses, adding pagination to an endpoint that returns everything, enforcing authorisation checks that were assumed rather than implemented, and writing the documentation that never existed. We scope it after reading the code, because an API that is merely untidy is a very different job from one that is structurally wrong.
How do you handle authentication and authorisation?
By matching what you already run wherever that is sensible. For first-party web and mobile clients, short-lived access tokens with refresh, stored where the platform makes them hardest to steal. For service-to-service traffic, credentials with narrow scopes rather than one key that can do everything. The authorisation half matters more and is the half usually done badly: every endpoint has to answer whether this particular caller may touch this particular record, not merely whether somebody is logged in.
What does good API documentation actually look like?
An OpenAPI document generated from the code or verified against it in the test suite, so it cannot drift, plus the parts a specification cannot express: what each error code means, what the rate limits are, how pagination behaves at the edges, and a worked example of the two or three flows most clients will implement first. Hand-written documentation that nothing checks is worse than none, because people believe it.
Send Us the Endpoints, or the Mess
A rough list of resources is enough to scope a new build. For an existing API, read access to the repository is enough to tell you whether this is a tidy-up or a rebuild.