API development
Laravel API Development
APIs other teams can build against - a contract generated rather than described, authentication with one owner, a versioning policy, and pagination that survives the data growing.
An API is a promise to somebody who is not in the room. That is the whole difference between building one and building the rest of an application: your own screens can change together with the code behind them, and a consumer cannot.
Most Laravel APIs we are asked to fix were built as though that were not true.
The contract is generated, not described
A specification written by hand in a wiki is out of date within two sprints, and the first person to discover that is a consumer who built against it.
So the contract comes from the code: typed resources, request validation the specification is derived from, and a generated OpenAPI document published as part of the build. A change to a response shape changes the document in the same pull request. It can be wrong only if the code is wrong.
Resources are explicit rather than a model handed to toJson(). Returning a
model directly means a column added for an internal reason becomes part of the
public contract on the day it is added - and removing it later is a breaking
change you never intended to make.
Versioning, decided before you need it
Version one is free and version two is not, so the decision worth making early is where the version lives and what counts as breaking.
Adding a field is not breaking. Removing one, renaming one, changing a type, or tightening validation is. Pagination shape, error shape and date format are part of the contract even though nobody writes them down as such - and each has broken a consumer at least once in our experience.
What we set up is a policy: additive changes ship continuously, breaking changes get a new version, both versions run in parallel for an agreed window, and consumers are told rather than discovered.
Authentication with one owner
Sanctum for first-party clients, Passport where OAuth2 is genuinely required, and in both cases one place that decides what a token can do.
The failure we find is duplication: a permission checked in a policy for the web routes and re-implemented in a middleware for the API routes, drifting apart until one of them is wrong. Authorisation belongs in policies that both entry points call, and the API tests assert the negative cases - not that the right person can read the record, but that the wrong person cannot.
Rate limiting per consumer rather than per IP, because per IP punishes an office and misses a script. Abilities scoped to what the token is for, so a leaked mobile token is not an admin token.
The parts that break under real data
Pagination. Offset pagination is fine until page four hundred, at which point the database is reading and discarding four hundred pages to give you one. Cursor pagination for anything that grows, decided at design time because changing it later is a breaking change.
Filtering and sorting. Every filter a consumer can pass is a query plan you have to be able to serve. An allow-list, with an index behind each entry, or you have published an endpoint that can be made arbitrarily slow by anyone holding a token.
Includes. Letting a consumer request nested relations is a good feature and an N+1 generator. Eager loading driven by the requested includes, with a depth limit, or the convenience becomes an outage.
Errors. One shape, documented, with a machine-readable code that does not change when the human-readable message does. A consumer parsing your error strings is a consumer you will break by improving your copy.
How an engagement runs
It starts with the consumers. Send us who calls this API, what they do with it, and the one endpoint that has caused the most support tickets. That is usually enough to size it.
What comes back is a written scope: the endpoint list, the authentication decision, the versioning policy, and what is in the first phase against what is deliberately not. It carries a price, and it is the document the contract refers to, so the thing you sign and the thing we build are the same thing.
Then the work, in your repository and your review process. The specification is generated and published from the first phase onward, so your consumers read it while the API is still being built.
What you receive
The API, the generated specification published where consumers can reach it, a test suite that covers the authorisation negatives and the pagination boundaries, rate limiting configured per consumer, and a written versioning policy that says what you will and will not change without notice.
Where the API is being added to an existing application, you also get the thing that work usually surfaces: a list of business rules that were living in controllers, and where they now live so that both entry points agree.
Sanctum or Passport settles how consumers authenticate, and it is much easier to settle than to change later. The harder question underneath it is whether Laravel is the right runtime for this API at all. And if the API's job is mostly to talk to somebody else's, integration is the closer fit.
