All notes
7 min read

API development: what to specify before you build one

A short spec that prevents most API rework: who calls it, the resources and errors, authentication, versioning, limits, documentation and tests. With an OpenAPI example and a checklist for briefing a vendor.

AAAsghar AliFounder & Lead Engineer · Daniotech
Eight tiles, each a question an API specification must answer. Callers: who calls it and how stable must it be. Resources: what are the nouns and their rules. Errors: what does every failure look like. Authentication: who is the caller and what may they touch. Versioning: what counts as a breaking change. Limits: rate, size, timeouts and pagination. Documentation: is the reference generated from the spec. Tests: how is the contract checked on every change.
Eight tiles, each a question an API specification must answer. Callers: who calls it and how stable must it be. Resources: what are the nouns and their rules. Errors: what does every failure look like. Authentication: who is the caller and what may they touch. Versioning: what counts as a breaking change. Limits: rate, size, timeouts and pagination. Documentation: is the reference generated from the spec. Tests: how is the contract checked on every change.

Most API rework is not a coding problem. It is a contract problem: two teams (or one team, a year apart) had different pictures of what a request should look like, what comes back when it fails, and what may change without warning. An API is a promise to whoever calls it. Writing that promise down before the code exists is the cheapest quality measure there is.

This post lists what to decide before you build or commission an API, gives a small example, and ends with a checklist for briefing a vendor. It is written for the person who owns the outcome, and it is just as useful as a review list for the person writing the code.

Start with who calls it

The same endpoint needs different care depending on its callers:

  • Your own web or mobile app. You control both sides and can change them together, but old app versions stay in the wild for a long time. Mobile users do not all update.
  • Partners or customers' systems. You cannot change their code. Stability, notice periods and good errors matter much more.
  • The public. Everything above, plus abuse: rate limits, keys, quotas and monitoring.

Write down the callers and the stability promise you are making to each. It decides most of the rest.

Write the contract first

Describe the API in a machine-readable format before writing the implementation. OpenAPI is the standard way to describe HTTP APIs. A description like this gives you something to review with non-engineers, and it can be used to generate a mock server for the front-end team, reference documentation, client libraries and tests.

openapi: 3.1.0
info:
  title: Bookings API
  version: 1.0.0
paths:
  /bookings:
    post:
      summary: Create a booking
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewBooking'
      responses:
        '201':
          description: Created
        '409':
          description: The slot is no longer available
        '422':
          description: The request was well-formed but failed validation
components:
  schemas:
    NewBooking:
      type: object
      required: [slotId, customerId]
      properties:
        slotId: { type: string }
        customerId: { type: string }

Notice that the example already answers a question a vague brief would leave open: what happens when two people book the same slot?

Resources and their rules

  • Name the nouns (bookings, customers, invoices) and their relationships. Keep URLs about resources and use HTTP methods for the actions: GET reads, POST creates, PUT or PATCH changes, DELETE removes.
  • Use status codes for what they mean: 201 created, 400 or 422 for bad input, 401 not authenticated, 403 not allowed, 404 not found, 409 conflict, 429 too many requests.
  • Make unsafe requests safe to retry. Networks fail, and clients retry. A payment or booking that runs twice is a bug. A common convention is an idempotency key sent with the request, so the server can recognise a repeat and return the first result.
  • Paginate lists from day one. Cursor-based pagination stays stable as data changes; offset pagination is simpler but can skip or repeat items when data moves. Set a maximum page size.
  • Decide filtering and sorting up front instead of adding options ad hoc.

Errors are part of the product

Callers spend a surprising amount of time on failures. Pick one error shape and use it everywhere. RFC 9457, Problem Details for HTTP APIs, defines a standard one:

{
  "type": "https://example.com/problems/slot-unavailable",
  "title": "The slot is no longer available",
  "status": 409,
  "detail": "Slot 42 was booked a moment ago. Choose another slot.",
  "instance": "/bookings"
}

sent with the content type application/problem+json. A small helper keeps every handler consistent:

function problem(res, status, title, detail, type = 'about:blank') {
  res.status(status).type('application/problem+json').json({ type, title, status, detail });
}

Never return stack traces or internal messages to callers. Log them with a request ID, and return the ID so support can find them.

Authentication and authorization

Two questions, often confused: who is calling? and what are they allowed to touch?

  • Authentication depends on the caller. First-party apps often use session cookies or short-lived tokens; partner and machine-to-machine access typically uses API keys or OAuth 2.0 with scopes.
  • Authorization has to be checked on every request, for every object. Broken object-level authorization, where a user changes an ID in a request and reads someone else's record, is the top risk in the OWASP API Security Top 10. It is a logic bug that a scanner will not always find, so write tests for it: "user A must not be able to read or change user B's booking".

Also decide where secrets live, how keys are rotated, and what the API logs about callers.

Versioning and change policy

Decide, before there are callers, what counts as a breaking change: removing or renaming a field, changing a type, tightening validation, changing the meaning of a value. Adding an optional field is normally safe. Then choose how versions are expressed (in the path or in a header), how long an old version is supported, and how you give notice before retiring one. For an API used by mobile apps, plan on old versions living a long time.

Limits

Every API needs written limits, or it will find them in production:

  • Rate limits per caller, with a 429 response and a Retry-After header.
  • Maximum request size and page size.
  • Timeouts: server-side, and what callers should expect.
  • Concurrency and any expensive operations that need to become background jobs.

Documentation and developer experience

Generate the reference from the same contract the code is checked against, so it cannot drift. Add real examples for the important flows, a changelog, and a way to try the API safely (a sandbox or test keys). Documentation is the API's first user interface.

Tests and operations

  • Contract tests that check responses against the specification on every change.
  • Integration tests against a real database, not only mocks.
  • Authorization tests for the "user A / user B" case above.
  • Load tests for the endpoints that will be busiest or most expensive.
  • Operations: structured logs with request IDs, metrics for latency and error rate by endpoint, alerts someone will act on, and a health endpoint.

Third-party integrations: assume they fail

If your API depends on someone else's (payments, maps, email, an ERP), plan for their failures, not just their happy path:

What goes wrong What to plan
The service is slow or down Timeouts, retries with backoff, a defined fallback
A call is repeated Idempotency keys, deduplication
A webhook arrives twice, late or out of order Verify the signature, store event IDs, make handlers idempotent
Their data does not match yours Validation at the boundary, and a place to park bad records
They change something Pin versions, monitor, and read their changelog

For pushing updates to clients, compare polling, webhooks, server-sent events and WebSockets; see WebSocket vs WebRTC for the messaging side.

REST, GraphQL or gRPC?

Use REST with a good OpenAPI description as the default: it is well understood and works everywhere. Consider GraphQL when many different clients need to shape their own queries. Consider gRPC for internal service-to-service calls where speed and strict typing matter. The choice matters less than writing the contract and following it.

A checklist for briefing a vendor

Give every vendor the same brief, and ask them to answer against it:

  1. Who calls the API, and how stable must it be for each of them?
  2. The resources and the main flows, with the rules for each.
  3. The systems it must integrate with, and who owns each one.
  4. Authentication and the roles or permissions model.
  5. Non-functional needs: expected load, latency, availability and data retention.
  6. The error format and the versioning policy you expect.
  7. Documentation, test coverage and the deliverables at handover: source, specification, environments and runbooks.
  8. Who runs it after launch, and what support you need.

A good vendor will push back on gaps in this list. Treat that as a good sign; see how to choose a development partner.

Frequently asked questions

Do we need an API specification for a small project? Even a one-page description of the resources, the errors and the authentication saves rework. Use the full format when other teams or partners will call it.

Who owns the API contract? Someone on your side should own it, even if a vendor writes it. It expresses your business rules.

How much does API development cost? It follows the scope: the resources, the rules, the integrations and the quality bar. See how to estimate the cost of an app; the same method applies.

Should the API and the app be built together? Usually, yes, against an agreed contract, so the front end can start on a mock while the backend is built.

Where to go from here

If you have a system that needs an API, or an API that has grown without a contract, send us what you have: the callers, the data and the integrations. We build APIs and integrations with Node.js, and we will reply with the questions we would ask before writing any code. Send requirements, or see our API development page.

  • #Custom software
  • #API
  • #Backend
  • #Node.js

Working on this?

Planning a custom software project?

Send us the problem, the users and the deadline. We will reply with the questions we would ask before writing any code.