API design
The moment someone else depends on your API, every field in it is a promise you have to keep or negotiate out of.
The hardest part is that an API cannot be quietly refactored. Internal code can be reshaped whenever you like; a public interface has consumers whose release schedule you do not control, some of whom will never update. That makes the initial shape unusually expensive to get wrong, and it makes versioning a thing you design at the start rather than invent during the first breaking change. Additive evolution — new optional fields, new endpoints, nothing removed — buys years before you need a version two.
REST or GraphQL is asked more often than it matters. GraphQL earns its complexity when many different clients need differently shaped subsets of a rich graph, and it brings real costs: query cost analysis, caching that no longer comes free from HTTP, and a schema that becomes its own governance problem. For a handful of clients consuming predictable resources, REST with well-chosen endpoints is simpler to build, cache, debug and explain — and simplicity in an interface other people depend on is a feature.
Everything else is operational and gets skipped until it hurts. Rate limits protect you from one client's retry loop taking down the service for everyone, and they need to be communicated in headers rather than discovered through failures. Idempotency keys let a client retry safely after a timeout without creating a second charge. Errors need to be machine-readable and specific, because "400 Bad Request" with no detail turns every integration into a support conversation.
How we work
- Versioning and an additive evolution policy are decided before the first consumer, not during the first breaking change.
- Idempotency keys on anything that creates or charges, so a client retry after a timeout is safe.
- Errors are specific and machine-readable, and the docs are generated from the same schema the server enforces.
What this includes
Pick what you need and send it over.