Skip to main content

API design

The Real API Contract Is the Lifecycle, Not the Endpoint

API integrations break after the first request: webhooks, retries, duplicate events, state changes, and lifecycle behavior.

May 1, 2026FetchSandbox Engineering

Most API docs are written like the request is the product.

Send this payload. Get this response. Here are the fields. Here is a curl example.

That helps you make the first call.

It does not help you ship the integration.

The real API contract is not the endpoint. The real contract is the lifecycle around it.

The first response is usually not the truth

A 200 OK or 201 Created tells you the API accepted something. It does not always tell you the job is done.

Payments are the obvious example.

You create a PaymentIntent. It returns successfully. But the payment might still need 3DS. It might move into requires_action. It might fail later. A webhook may arrive after your UI already told the customer everything worked.

SMS is similar.

Twilio can accept a message and return a SID. Your app stores it as “sent.” Then the carrier rejects the message later and the real status becomes undelivered.

Billing has the same shape.

Paddle can send subscription events out of order. subscription.activated can arrive before subscription.created. If your handler inserts on one event and updates on another, one event can silently do nothing.

None of these bugs live in the first request.

They live in the state that comes after it.

Docs usually explain endpoints. Production tests workflows.

The difference sounds small until you are debugging a real integration.

Endpoint documentation answers:

  • What URL do I call?
  • What headers do I send?
  • What fields are required?
  • What does the example response look like?

Workflow documentation answers:

  • What states can this resource move through?
  • Which state changes trigger webhooks?
  • Can webhooks arrive late, duplicated, or out of order?
  • What should I do if I get the webhook before the object exists in my database?
  • What happens if the first request succeeds but the downstream action fails?
  • How do I replay or simulate that exact branch locally?

Most docs are strong on the first list and vague on the second.

That gap is where integrations drift from the provider’s state.

Webhooks are not queues

This is one of the most expensive assumptions in API integrations.

Developers often build handlers like this:

if (event.type === "subscription.created") {
  await db.insert(subscription)
}

if (event.type === "subscription.activated") {
  await db.update(subscription.id, { status: "active" })
}

It reads fine. It matches the lifecycle diagram in your head.

Then subscription.activated arrives first.

The update matches zero rows. No error. No crash. Nothing obvious in logs unless you were looking for affected row count. Later, subscription.created inserts the row with the old status.

Now your app says the customer is pending while the billing provider says the customer is active.

That is not a schema problem. It is a lifecycle problem.

The fix is not “read the OpenAPI spec harder.” The fix is to design for the real delivery contract:

  • events can arrive out of order
  • events can be duplicated
  • events can be delayed
  • events can be missing and need reconciliation
  • every handler should be idempotent
  • every important state needs a source of truth

Good webhook docs say this clearly and provide examples for it.

Great webhook docs give you a way to test it.

Sandboxes should test the uncomfortable branch

A lot of API sandboxes prove the happy path.

You create a resource. You fetch it. It exists. Nice.

That is useful, but it is not enough.

The bugs teams remember are usually in the uncomfortable branch:

  • The payment needs authentication after your first success response.
  • The SMS is accepted but later becomes undelivered.
  • The subscription cancellation webhook is missed.
  • The webhook is replayed twice.
  • The status update arrives before the create event.
  • The API returns 200, but the resource is not visible in search yet.

If a sandbox cannot reproduce those branches, developers still have to learn them in production.

That is a bad trade.

This matters more now because agents are writing integration code

Coding agents are getting good at following API references.

Give an agent an OpenAPI spec and it can generate a client, call endpoints, and wire up basic CRUD faster than most people.

But agents cannot infer undocumented lifecycle behavior.

If the docs only say:

POST /messages -> 201 Created

the agent may treat 201 as done.

If the docs say:

POST /messages -> message.status = queued
later status can become sent, delivered, failed, or undelivered
status callbacks may arrive asynchronously
your app should reconcile by SID

the agent has a chance to build the right shape.

OpenAPI describes endpoints.

Future API platforms need to describe workflows.

What better API platforms should expose

If I were reviewing an API platform from an integration reliability point of view, I would look for these things:

  • Lifecycle state diagrams for important resources.
  • Clear webhook delivery guarantees.
  • Duplicate and out-of-order webhook examples.
  • Idempotency examples for every create or mutation endpoint.
  • Retry behavior that is documented with timing and final states.
  • Test credentials that trigger realistic failures, not just validation errors.
  • Replayable webhook payloads with signatures.
  • A sandbox that persists state across requests.
  • Examples that show the second read, not just the first write.
  • Workflow-level specs that coding agents can consume.

This is not just developer experience polish.

It is production risk reduction.

Every missing lifecycle detail becomes tribal knowledge inside customer engineering teams.

The endpoint is where integration starts

The lifecycle is where it either works or breaks.

That is the shift I think more API companies need to make.

Do not only document the request.

Document what can happen after the request. Show the retries. Show the webhook timing. Show the state transitions. Show the failure path. Show how to recover.

Because the painful part of API integration is rarely “how do I send the payload?”

It is “how do I know my system still matches your system after the first response?”

That is the real contract.