---
name: api-patterns
description: "Choosing an API style (REST, GraphQL, gRPC, webhooks, events) and the contracts every style needs: versioning, cursor pagination, idempotency keys, RFC 9457 errors. Use when deciding how two systems talk or how a contract evolves."
metadata:
  risk: safe
  source: multi-agent-pipeline
  date_added: "2026-09-25"
  not-for: rest-api-design
---

# API Patterns

An interface outlives the code on both sides of it. Clients ship on their own
schedule, mobile builds stay installed for years, and partner integrations get
written once and forgotten. The decisions here are the ones that are expensive
to reverse, so make them deliberately and write the reasoning down.

This skill covers the choice of style and the contracts that apply whatever
style wins. REST mechanics (URLs, verbs, status codes, cache headers, OpenAPI)
live in `rest-api-design`; security controls live in `api-security-best-practices`.

## 1. Start from the consumers, not the technology

Answer these before naming a style:

- **Who calls it?** Your own frontend, other internal services, third parties
  you do not control, or devices that update rarely.
- **What is the interaction shape?** Ask-and-answer, a long-running job, a
  notification that something happened, or a continuous feed.
- **Who owns change?** If one team owns both ends, coupling is cheap. If the
  caller is outside your release train, every change is a negotiation.
- **What does the network look like?** Browser, mobile on flaky links,
  service mesh inside one data centre.

## 2. Choosing a style

| Situation | Reasonable default | Why |
|---|---|---|
| Public or partner API, broad tooling expected | REST over HTTP with an OpenAPI document | Every language and proxy understands it; caching works |
| One product team owns a rich client with many screens pulling nested data | GraphQL | Client picks fields; one round trip per screen |
| Internal service-to-service calls with strict latency budgets | gRPC with protobuf | Typed contracts, code generation, HTTP/2 streaming |
| TypeScript monorepo where server and client ship together | tRPC or a shared schema package | Types flow end to end without a separate IDL |
| "Tell me when X happens" to an external party | Webhooks | Caller does not poll; you push on change |
| Many internal consumers reacting to the same fact | Events on a log or broker (Kafka, NATS, SQS/SNS) | Producers stay unaware of consumers |
| Work that takes longer than a request should | Accept, return a job resource, then poll or notify | Keeps request timeouts honest |

Mixing is normal: a public REST API, gRPC between internal services, and an
event stream for side effects is a common and healthy shape. What goes wrong is
picking GraphQL or gRPC for a public surface because it is fashionable, then
rebuilding caching, auth and tooling that REST gave for free.

### GraphQL specifics worth deciding up front

- Depth and complexity limits, or one query can fan out into thousands of
  resolver calls.
- A batching layer (DataLoader or equivalent) so a list of 50 orders does not
  run 50 customer lookups.
- Persisted queries for production clients, which also shrink the attack surface.
- Errors: GraphQL returns 200 with an `errors` array; decide which failures are
  field-level and which reject the whole operation.

### gRPC specifics

- Never reuse or renumber a protobuf field tag; mark removed ones `reserved`.
- Set deadlines on every call and propagate them downstream.
- Browsers need gRPC-Web or Connect; plan a gateway if a web client is coming.

## 3. Evolving a contract

Additive changes (optional fields, new endpoints or event types, enum values
clients were told to tolerate) ship without a version; removals, renames, type
or meaning changes need a migration plan. Prefer additive-only, then dated
versions, then a path major version. Announce deprecation with `Deprecation`
and `Sunset` headers, measure remaining callers, and check event schema
compatibility in CI. Details: `references/contract-details.md`.

## 4. Contracts every style needs

Load `references/contract-details.md` for examples. The rules:

- **Pagination**: opaque cursor over the sort key plus a unique tiebreaker,
  server-capped `limit`, no total count unless it is cheap; offset paging only
  for small, static data.
- **Idempotency**: non-idempotent writes accept an `Idempotency-Key` scoped to
  the caller. Replay the stored result for a completed key, `409` while it is
  still running, `422` for the same key with a different body; store key and
  result in the side effect's transaction and expire them after a stated window.
  Consumers deduplicate on the event id.
- **Errors**: one RFC 9457 problem-details shape (`application/problem+json`)
  with a stable `type`, field errors as JSON Pointers, a request id, and no
  internals. gRPC uses canonical codes plus `google.rpc` details; events go to a
  dead-letter destination with the reason.
- **Webhooks**: HMAC-signed timestamp plus body with replay rejection, retries
  with backoff and jitter then parking, a documented thin or full payload, and
  replay on request.

## 5. Checklist before publishing a contract

- [ ] Style chosen from consumer and interaction shape, reasoning recorded (ADR).
- [ ] Machine-readable schema exists: OpenAPI, GraphQL SDL, `.proto`, or event schema.
- [ ] Unknown fields and enum values are tolerated by clients, and documented as such.
- [ ] List endpoints paginate with a capped cursor.
- [ ] Non-idempotent writes accept an idempotency key.
- [ ] Every error uses the shared problem shape with a stable `type`.
- [ ] Deprecation policy is written down with a minimum notice period.
- [ ] A contract test or breaking-change check runs in CI.

## References

- `references/contract-details.md`: pagination, idempotency, problem-details and webhook examples in full.

## Related skills

- `rest-api-design`: URLs, methods, status codes, caching headers, OpenAPI.
- `api-security-best-practices`: authentication, authorization, rate limits.
- `architecture`: recording the decision as an ADR.
- `nodejs-backend-patterns`, `fastapi-pro`: implementing the chosen contract.
