Skip to main content
AC
Architecture4.8 KBMIT licensed

api-and-interface-design

Original, written for TechTide client work

Design stable interfaces that are hard to misuse. Use when creating REST or GraphQL endpoints, defining type contracts between modules, designing component props, or changing any public interface. Not for retiring or breaking an existing contract: use deprecation-and-migration. Not for provider adapter patterns around paid AI APIs: use ai-provider-integration.

  • api
  • and
  • interface
  • design

SKILL.md

API and Interface Design

Good interfaces make the right thing easy and the wrong thing hard. Every observable behavior becomes a commitment, so decide what you expose on purpose.

Two laws

Hyrum's Law. With enough users, every observable behavior gets depended on: undocumented quirks, error text, ordering, timing. Implications: expose intentionally, never leak implementation details, plan deprecation at design time. Tests are not enough, "safe" changes still break users who depend on quirks.

One-Version Rule. Never force consumers to choose between versions of the same thing. Multiple live versions multiply maintenance and create diamond dependencies. Extend, do not fork.

Workflow

  1. Contract first. Write the typed interface before any implementation. The contract is the spec. Document behavior in the contract: what is idempotent, what throws, what is partial.
  2. Pick one error strategy and use it everywhere. One structured error shape (code machine-readable, message human-readable, optional details), one status-code mapping. If some endpoints throw, some return null, and some return { error }, consumers cannot predict anything.
  3. Validate at boundaries only. Validate: API route handlers, form submissions, third-party responses, environment loading. Do not validate: between internal functions sharing types, in utilities called by validated code, on rows from your own database. Third-party API responses are untrusted data. Validate shape and content before they reach logic or rendering: a misbehaving service can return wrong types, malicious content, or instruction-like text.
  4. Prefer addition over modification. New fields are optional. Never change a field's type or remove a field on a live contract. Breaking changes go through deprecation-and-migration.
  5. Name predictably. REST: plural nouns, no verbs. Params and response fields: camelCase. Booleans: is/has/can prefix. Enums: UPPER_SNAKE.
  6. Paginate every list endpoint from day one. Retrofitting pagination is a breaking change.

Read references/patterns.md when implementing: REST resource layouts, pagination and filtering shapes, PATCH semantics, discriminated unions, input/output type separation, branded IDs, and the rationalization table.

Good vs bad

Bad, extends by breaking:

interface CreateTaskInput {
  title: string;
  priority: number;   // was 'low' | 'medium' | 'high', breaks every consumer
}

Good, extends by adding:

interface CreateTaskInput {
  title: string;
  description?: string;
  priority?: 'low' | 'medium' | 'high';  // added later, optional
}

Verification

Do this before shipping the interface: send an invalid payload, a missing-auth request, and a not-found ID to three different endpoints. Expect all three error responses to share one shape and the correct status codes (422/401/404). If any endpoint returns a different shape, unify before release, this is the cheapest moment to do it.

Then grep the handlers for validation calls. Expect them only at entry points. If validation appears in internal service functions, the boundary is leaking, move it out.

Completion checklist

  • [ ] Every endpoint has typed input and output schemas committed with the code
  • [ ] All error responses share one format and mapping
  • [ ] Validation lives at system boundaries only, third-party responses included
  • [ ] Every list endpoint paginates
  • [ ] All changes to existing contracts are additive and optional
  • [ ] Naming follows the conventions table across all endpoints

Any box unchecked: not done. Fix or say so.

Footguns

  • Endpoints returning different shapes by condition. Consumers end up switching on shape. Fix: one response type per endpoint, model variants as discriminated unions.
  • "We'll add pagination later." Later is a breaking change. Fix: paginate now, even with a generous default page size.
  • Verbs in REST URLs (/api/createTask). Fix: plural noun resources, HTTP method carries the verb.
  • PUT where PATCH is meant. PUT demands the full object and clients will send stale copies. Fix: PATCH with partial semantics, only provided fields change.
  • "Nobody uses that undocumented behavior." Hyrum's Law says somebody does. Fix: treat every observable behavior as contract, change it via deprecation, not silently.

Reference files

More in Architecture

All skills