Architecture
Designing APIs that other systems can actually use
Most APIs are designed by the team that already understands the domain. That is why they are hard to consume. The resource names make sense internally. The error bodies are whatever the framework emitted. Pagination is different on every endpoint. A partner engineer has to reverse-engineer the product from a Postman collection.
Start from the consumer’s job
Write down the three things an external system must be able to do. “Create an order, fetch its status, and cancel it” is a job. “Expose our internal models” is not. If a consumer needs to join four resources to complete one action, the API is reflecting your tables, not their workflow. That might be acceptable internally. It is a poor partner contract.
Make the boring parts identical
- One authentication scheme, documented with a failed-request example
- Error shapes that include a stable code, a human message, and a request id
- Idempotency keys on anything that creates money, inventory, or tickets
- Pagination and filtering that work the same way on every list
- Webhooks that retry, sign payloads, and can be replayed
Versioning is a promise, not a folder name
Putting /v1/ in the URL does nothing if you change the meaning of a field in place. Version when you cannot keep old clients working. Until then, add fields, do not redefine them. If you must break a contract, give consumers a date and a dual-running period. Silent breakage is how integrations become folklore.
Operability is part of the interface
The other system will fail. Your job is to make that failure inspectable: logs with the request id, a dead-letter view, and the ability to replay. An API without operational tooling is a support burden waiting for the first peak hour.
If you are making a similar decision on a product or internal system, tell us about the workflow. Related reading lives with our services.