Skip to main content
Engineering 10 min read

Well-designed APIs: contracts before code

Designing an API means writing a contract between teams, products and companies. Code can be rewritten; a contract, once published and integrated, changes at a cost and requires negotiation. That's why it's worth investing time in the format before investing time in the implementation.

Start from the consumer, not from the database

An API that mirrors tables forces every client to rebuild the business rule on its own. An API that exposes meaningful domain operations reduces coupling and prevents the same logic from being reimplemented in five different places.

A simple exercise helps: write the call and response example you'd like to read in the documentation first. If the example is confusing, the design isn't ready yet.

  • Stable, predictable names, consistent in language across the whole surface.
  • Errors with a code, a readable message and a correlation identifier.
  • Pagination, sorting and filters defined from the first version.

Errors and idempotency are part of the product

Networks fail, clients retry, timeouts happen. Write operations need an idempotency key so a retry doesn't produce two charges or two orders. That detail prevents most integration incidents.

Error responses deserve the same care as success responses: predictable, documented and free of leaked internal information.

Versioning means planning for change

Adding a field is safe; removing one or changing its meaning is not. When a breaking change is unavoidable, publish a new version, keep the previous one for an announced period, and offer a migration path. A written deprecation policy is worth more than any verbal promise.

Living documentation and explicit limits

A specification generated from the code, executable examples and a test environment drastically reduce integration cost. Usage limits, quotas and authentication rules should be on the first page, not in a footnote.

In short

A good API is one someone can integrate without opening a support ticket: clear contract, predictable errors, idempotency and a published versioning policy.

Use cases

Integration with a client's ERP

Stable contract, per-partner key and safe reprocessing of duplicate events.

Webhook for external systems

Verified signature, retries with progressive backoff and logging of every attempt.

Mobile app

Lean responses, tolerant versioning and compatibility with older versions still installed.

Common mistakes

  • Exposing the database model directly in the response.
  • Returning success with an error message inside the body.
  • Breaking a contract with no warning and no new version.
  • Documenting only the happy path.

Best practices

  • Contract defined and reviewed before implementation.
  • Idempotency key on every relevant write operation.
  • Clear usage limits and authentication rules per consumer.
  • Contract tests run in the pipeline.

Recommended books

  • Building Microservices Sam Newman

    Covers integration, contracts and service boundaries with a practical focus.

  • Domain-Driven Design Eric Evans

    Grounds how to expose domain operations instead of internal structures.

  • Release It! Michael Nygard

    Shows resilience patterns essential for real-world integration.

Go deeper

Frequently asked questions

REST or GraphQL?
It depends on consumption. REST tends to be simpler to operate and cache; GraphQL helps when very different clients need different slices of the same data.
When should I create version 2?
When a change breaks existing clients. Adding an optional field usually doesn't require a new version.
What is idempotency?
The guarantee that repeating the same call produces the same effect, avoiding duplication in case of a network failure.
How do I document without it becoming endless manual work?
By generating the specification from the code and validating examples in the pipeline, so the documentation doesn't drift from reality.

References

Original content by the i9 Conecty team. Classic concepts are explained in our own words and credited to their authors.

Next in the trackDevOps in practice: continuous delivery without dramaShipping should be the most boring part of the day. When shipping feels scary, the problem isn't the tool — it's the process.

Keep reading