Skip to content

3 September 2026 · 4 min read

Building an API other people will use

An internal API that becomes a partner API exposes every undocumented assumption. What to settle before the first external caller.

Author
Sawhorse Engineering
Published
3 September 2026

An API built for one internal caller becomes a partner API when someone asks, and at that moment every assumption nobody wrote down turns into a support ticket. The work below is cheap in advance and expensive afterwards.

The specification is the contract

Write it before implementing. Resources, fields, types, status codes, error bodies, pagination, filtering and authentication, agreed and published.

Two reasons. First, both sides can work in parallel against an agreed shape, which is usually the schedule win that justifies the exercise. Second, a written specification is the thing you can point at when there is a disagreement about intended behaviour.

Generate the reference documentation from the specification so the two cannot drift. Documentation maintained separately from the contract is documentation that is wrong within a quarter.

Version from the first release

Put a version in the path or a header from day one, even when there is only one.

Additive changes ship inside a version: new optional fields, new endpoints, new enum values that consumers are told to tolerate. Breaking changes get a new version with an overlap period and a written migration note.

Retrofitting versioning is painful, because the un versioned callers exist and you cannot identify them. Doing it at the start costs nothing.

Error responses need structure

A string is not an error format. Consumers have to handle errors programmatically, which means they need a stable machine readable field.

Return a stable error code, a human readable message, and where relevant the field that caused it. The code is what a consumer branches on, the message is what they log, and the field is what they show a user.

Use status codes deliberately. A validation failure is not a server error. Missing authentication and insufficient permission are different cases. A service may intentionally return 404 to conceal a forbidden resource's existence, as allowed by HTTP semantics. Keep that policy consistent and document what callers can rely on.

Pagination and filtering

Every collection endpoint needs pagination from the first release, because the caller who has thirty records today will have three hundred thousand later.

Prefer cursor based pagination for large collections. Offset pagination degrades as the offset grows and skips or duplicates records when the underlying data changes between pages.

Filtering should be explicit and documented rather than accepting arbitrary query expressions. An open query interface is a performance incident waiting for a caller with an unusual idea.

Authentication for machines

API keys or OAuth clients, with scopes, so a consumer that only reads cannot write.

Support rotation without downtime: two active keys, so a consumer can add the new one before removing the old. Without that, every rotation is an outage for someone.

Rate limit per client, and return a proper status code with a header telling the caller when to retry. Dropping requests silently teaches consumers to retry aggressively, which makes the problem worse.

Idempotency on every write

Accept a caller supplied key on every operation that creates or changes something. Store the key, a fingerprint of the request and the response.

A retry with the same key returns the original response. Without it, every timeout on the caller side risks a duplicate on yours, and the caller cannot safely retry at all.

Webhooks if you have events

If consumers need to know when something changes, polling is a poor substitute for delivery.

Sign the payload with a per endpoint secret, include a timestamp and a stable event identifier, retry with backoff over a generous window, and give consumers a delivery log they can inspect plus a replay endpoint. Everything you would want from a provider, provide.

A sandbox that works

Documentation with examples that cannot be run is documentation people distrust.

A test environment with realistic seeded data, credentials a developer can get without a sales conversation, and a way to trigger the events they need to handle. The time a consumer spends integrating is a cost you pay in support, and a good sandbox reduces it more than any other single thing.

Deprecation with notice

When a version is retired, consumers need warning, a migration note, and ideally usage data showing what they still call.

A deprecation header on responses, a documented timeline and a direct message to identified consumers. Turning something off without that is how an integration becomes a support incident.

Object permissions need a test with another customer's identifier

A valid API key establishes a caller, not permission to every object in the database. Test the same read and write using an identifier belonging to a different tenant. Also test nested resources: an attachment identifier must belong to the order the caller may access, not merely exist somewhere in storage.

Apply the scope at the data access boundary. Filtering a list while leaving the detail endpoint unrestricted creates a discovery problem, not an access control system. Export jobs, search results and webhook subscriptions need the same tenant boundary as ordinary reads.

For long running operations, return an operation identifier and expose an authorised status resource. Distinguish accepted work from completed work, and explain whether cancellation is still possible. A timeout should lead a caller to retrieve the existing operation rather than start another export or import.

Make the reference examples exercise those distinctions. Include a successful request, invalid input, an operation still processing and an access denial. The examples teach consumers how to recover without guessing. Run them as contract tests so that changing a response field also changes the evidence that the published interface still works.

Let’s talk about the system you need.

Tell us where the current process breaks down. We can map the workflow, examine the integration requirements and define a practical scope for the build.

  • A mutual NDA is signed before project details are shared.
  • Scope is fixed in writing before any build starts.
  • You own the source code, the IP and the documentation at delivery.
  • Support plans are available and tailored on a discovery or technical call.