API Design Review skill for Codex

Catch contract mistakes before the first client. Codex reviews the contract rather than the code: it lints OpenAPI with Redocly or Spectral, diffs breaking changes with oasdiff, buf breaking or GraphQL Inspector, then runs design passes for resource model, error shape, pagination, idempotency, evolution and authorization. Each blocker names the exact path or field, the client-facing consequence and a corrected contract snippet.

  • Architecture
  • $api-design-review
  • 142 lines
  • 3 files
  • ~1,850 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- api-design-review

Installs to ~/.agents/skills/api-design-review/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $api-design-review, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Reviews proposed APIs before they are built - REST/OpenAPI, GraphQL schemas and gRPC protobuf - for resource modelling, naming consistency, error format, pagination, idempotency, versioning and evolution, auth scopes, rate limits and consumer ergonomics, using Spectral or Redocly lint, oasdiff, buf breaking and GraphQL Inspector to back findings. Use when asked to review an OpenAPI spec, a .proto file, a GraphQL schema or an API design doc, to check a PR that changes a public contract, or to decide whether a change is breaking.

Use it when

  • A new API contract or design doc needs review before implementation
  • A PR changes an OpenAPI spec, .proto file or GraphQL schema
  • You need to know whether a contract change breaks existing clients
  • Several teams' APIs need consistent conventions

Use something else when

How to check the result

Linters and breaking-change diff tools pass or every warning is explained, and each blocker's proposed snippet re-lints cleanly in a scratch copy of the spec.

The mistake it prevents

Shipping list endpoints that return bare arrays and creating POSTs without idempotency keys, both of which are breaking to fix once clients exist.

~/.agents/skills/api-design-review/SKILL.md

--- name: api-design-review description: Reviews proposed APIs before they are built - REST/OpenAPI, GraphQL schemas and gRPC protobuf - for resource modelling, naming consistency, error… license: MIT ---

Done well means the review catches what is expensive to change after the first client ships: resource shape, identifiers, error contract, pagination, idempotency, evolution rules and auth model. Each finding names the exact path, field or RPC, explains the consumer-facing consequence, and proposes the corrected contract. Style nits come last.

Before reviewing

  1. Read AGENTS.md and look for an API style guide (docs/api-guidelines.md, .spectral.yaml, redocly.yaml, buf.yaml). Review against the house rules first; general best practice second.
  2. Get the contract, not only the code: openapi.yaml, *.proto, schema.graphql, or generate it (framework OpenAPI export, buf build, schema introspection).
  3. Get the base version for diffs: git show origin/main:api/openapi.yaml > /tmp/base.yaml.
  4. Identify consumers: public third parties, mobile apps (old versions live for years), internal services, frontend. Public and mobile consumers raise the bar on compatibility.
  5. Read one or two existing endpoints in the same API so the review enforces consistency.

Run the tools first

Automated checks catch the mechanical issues so the human review can focus on design. These commands download packages on first run; with network off in the sandbox, ask for approval or skip and note it.

# OpenAPI
npx @redocly/cli lint api/openapi.yaml
npx @stoplight/spectral-cli lint api/openapi.yaml   # needs a .spectral.yaml, e.g. extends: ["spectral:oas"]
oasdiff breaking /tmp/base.yaml api/openapi.yaml    # Go binary; install via brew or go install

# Protobuf / gRPC
buf lint
buf breaking --against '.git#branch=main'

# GraphQL
npx @graphql-inspector/cli diff /tmp/base.graphql schema.graphql

Report tool findings grouped, not one comment each.

Review passes, in order

#PassQuestions
1FitDoes the API serve the actual consumer use cases? Can each key screen or workflow be built without N+1 calls or over-fetching?
2Resource modelAre nouns the domain’s nouns? Are identifiers opaque, stable strings? Are relationships navigable?
3OperationsRight method/mutation/RPC per action; safe and idempotent where they should be; long-running work modelled as an operation resource
4ErrorsOne error shape everywhere (RFC 9457 problem details for HTTP, google.rpc.Status with details for gRPC, typed error results or extensions.code for GraphQL); machine-readable codes; no stack traces
5CollectionsPagination on every list from day one (cursor by default), max page size, stable sort, filtering syntax
6Writes and retriesIdempotency keys on creating POSTs and mutations that charge, send or provision; optimistic concurrency (ETag/If-Match or version field) for updates
7EvolutionAdditive changes possible without a new version? Enums open for new values? Required fields minimal?
8SecurityAuth scheme declared; per-operation scopes or permissions; object-level authorization implied by the design; no secrets or PII in URLs; rate limits documented
9Consistency and namingCasing, pluralisation, timestamp format (RFC 3339 UTC), money (minor units plus currency), booleans named as states
10DocumentationEvery operation, field and error has a description and an example a client developer can copy

Per-protocol checklists with the specific traps for REST, GraphQL and gRPC are in references/protocol-checklists.md. Read the section for the protocol under review before writing findings.

Breaking change rules

A change is breaking if any existing, correctly written client can fail. Common cases:

ChangeREST/JSONGraphQLgRPC/protobuf
Remove or rename a fieldBreakingBreaking (deprecate first)Breaking; reserve the number and name
Add an optional field to a responseSafe if clients ignore unknown fieldsSafeSafe
Add a required request fieldBreakingBreaking (new non-null argument without default)Proto3 has no required; semantic breaks still count
Change a field typeBreakingBreakingBreaking (wire or JSON incompatibility)
Add an enum valueBreaking for clients with exhaustive switches; document enums as openBreaking for exhaustive clientsSafe on wire; old clients see the unknown number
Make a nullable response field non-nullSafeSafen/a
Make a non-null response field nullableBreakingBreakingn/a
Change default, sort order or pagination semanticsBehavioural breakBehavioural breakBehavioural break

When a break is needed: add the new shape alongside the old, deprecate with a date (Deprecation and Sunset headers for HTTP, @deprecated(reason:) in GraphQL, deprecated = true option in proto), measure usage of the old shape, remove after the date.

Writing findings

Label severity as in code review: blocker (will break clients, lose data, or open a security hole), major (expensive to change after launch), minor, nit, question.

**blocker:** `POST /v1/payments` has no idempotency mechanism. A mobile client that
retries after a timeout creates a second payment. Accept an `Idempotency-Key` header,
store the first response for 24 h, and return it on replays with the same key and body.
**major:** `GET /v1/orders` returns a bare array with no pagination. Adding pagination
later changes the response type and breaks every client. Return
`{ "data": [...], "next_cursor": "..." }` with `limit` (default 50, max 200) now.

Propose the corrected contract snippet for every blocker and major.

Gotchas

  • Reviewing the implementation instead of the contract: generated specs often omit error responses the code actually returns. Compare a few real responses with the spec.
  • Integer ids that leak volume and invite enumeration; prefer opaque string ids, and never trust ids in the URL without object-level authorization.
  • Floats for money; timestamps without time zone; durations without units in the name.
  • Versioning everything up front (/v1) is fine, but versioning is not a substitute for additive evolution; most changes should never need /v2.
  • One giant PATCH that accepts any field bypasses per-field authorization; check who may change each writable field.

Verification

  • Lint and breaking-change tools pass, or each remaining warning is explained.
  • For each blocker and major, the proposed snippet validates (re-run the linter on the edited spec in a scratch copy).
  • Walk two real consumer workflows end to end against the contract and list the calls; each workflow should need a small, fixed number of calls.

Output template

## API design review: <API name> (<OpenAPI | GraphQL | gRPC>), base <ref>
**Consumers:** <public, mobile, internal>
**Verdict:** ready | ready after blockers | needs redesign
**Tool results:** redocly 0 errors / 4 warnings; oasdiff: 1 breaking change (listed below)

### Blockers
1. `<path or field>` - <problem>, consequence for clients, proposed contract.
### Major
### Minor and nits (grouped)
### Questions for the API owner
### Not reviewed
<areas skipped, tools not run because network was unavailable>

What the zip contains

Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "API Design Review"
  short_description: "Catch contract mistakes before the first client"
  default_prompt: "$api-design-review Review this OpenAPI spec for our public payments API before we build it"
references/protocol-checklists.md129 lines, 6.5 KB
# Protocol checklists for API design review

Read the section for the protocol under review. Each item is a question to ask of the
contract; a "no" is a candidate finding.

## REST / OpenAPI

### Resources and URLs
- Plural nouns for collections (`/orders`), ids in the path (`/orders/{order_id}`), no
  verbs in paths except for explicit actions modelled as sub-resources
  (`POST /orders/{id}/cancel` is acceptable when cancel is not a plain field update).
- Nesting at most one level deep (`/customers/{id}/orders`); deeper hierarchies become
  filters (`/orders?customer_id=`).
- Path and query parameter casing consistent with the rest of the API (snake_case or
  camelCase, one of them).

### Methods and status codes
- `GET` safe, `PUT` and `DELETE` idempotent, `POST` for create and non-idempotent actions,
  `PATCH` with a documented format (JSON Merge Patch, RFC 7396, is the common choice).
- `201 Created` with `Location` for creates; `202 Accepted` plus an operation resource for
  long-running work; `204` for empty success.
- `400` malformed, `401` unauthenticated, `403` forbidden, `404` not found (also used to
  hide existence from unauthorised callers), `409` conflict, `412` precondition failed
  (stale `If-Match`), `422` validation error, `429` with `Retry-After`.
- Every operation declares its error responses in the spec, not only `200`.

### Errors
- One error schema, ideally RFC 9457 `application/problem+json`: `type`, `title`,
  `status`, `detail`, `instance`, plus extension members such as `errors` for field-level
  validation (`[{ "field": "email", "code": "invalid_format" }]`).
- Stable machine-readable codes clients can switch on; human text may change.

### Collections
- Cursor pagination (`limit`, `cursor`/`next_cursor`) by default; offset only for small,
  stable admin lists.
- Documented default and maximum page size; deterministic sort with a tie-breaker.
- Filtering and sorting parameters named consistently across list endpoints.
- Total counts optional (expensive on large tables); do not promise them by default.

### Writes
- `Idempotency-Key` header on creating POSTs that cause side effects; behaviour on key
  reuse with a different body defined (`422` or `409`).
- Optimistic concurrency: `ETag` on reads, `If-Match` required on updates of contended
  resources.
- Server-generated fields (`id`, `created_at`) marked `readOnly`; secrets `writeOnly`.

### Data formats
- Timestamps RFC 3339 in UTC (`2026-10-28T14:03:00Z`); dates as `YYYY-MM-DD`.
- Money as integer minor units plus ISO 4217 currency, or a decimal string; never float.
- Enums documented as extensible if they may grow.
- `nullable` versus absent semantics defined, especially for PATCH.

### Spec hygiene
- `operationId` unique and stable (code generators use it).
- Reusable schemas in `components`; no anonymous inline duplicates.
- Examples for every request and response body.
- `securitySchemes` defined and applied per operation with scopes.

## GraphQL

### Schema shape
- Types model the domain, not database tables; no `userId: ID` without a `user: User`
  field to navigate.
- Nullability chosen deliberately: fields that can fail independently (from other
  services) are nullable so one failure does not null the whole parent; ids and
  invariants are non-null.
- Lists of objects use connections (`edges`, `node`, `pageInfo`, cursor arguments
  `first`/`after`) or a consistent simpler pagination pattern; never unbounded lists of
  growing data.
- Global object identification (`Node` interface, `node(id:)`) if clients cache
  normalised data (Relay, Apollo).

### Mutations
- One input object argument (`input: CreateOrderInput!`) and a payload type
  (`CreateOrderPayload`) rather than returning the bare object, so fields such as
  `userErrors` can be added later.
- Expected business errors returned in the payload (`userErrors { field code message }`)
  or as union result types; transport errors in the `errors` array.
- Idempotency for side-effecting mutations (client mutation id or idempotency key input).

### Evolution
- Deprecate with `@deprecated(reason: "Use totalPrice; removal after 2027-03-01")`
  before removing; track field usage from operation logs.
- New arguments are optional or have defaults.
- Adding enum values and union members can break exhaustive client code; announce them.

### Safety and performance
- Query depth and complexity limits, and persisted or trusted operations for public
  first-party clients.
- Resolvers batched (DataLoader or equivalent); the schema review should flag fields
  that imply N+1 lookups at list scale.
- Field-level authorization for sensitive fields; introspection policy decided for
  production.

## gRPC / Protocol Buffers

### Messages
- Field numbers never reused; removed fields go into `reserved` (both number and name).
- Field types never changed in place; add a new field instead.
- Each RPC has its own request and response message (`GetOrderRequest`,
  `GetOrderResponse`), even if they start with one field, so they can grow.
- Enums start with a `*_UNSPECIFIED = 0` value; values prefixed with the enum name.
- Use well-known types: `google.protobuf.Timestamp`, `Duration`, `FieldMask` for partial
  updates, wrapper types or `optional` when presence matters.
- Money via a type with units and currency (for example `google.type.Money`), not floats.

### Services and methods
- Method names are verbs plus resource (`CreateOrder`, `ListOrders`); standard methods
  follow Google AIP conventions if the team uses them (`List` with `page_size`,
  `page_token`, `next_page_token`).
- Streaming chosen deliberately; server streaming for feeds, not as a substitute for
  pagination of finite lists.
- Long-running work returns an operation (`google.longrunning.Operation`) or an id to poll.
- Deadlines expected on every call; document reasonable values per method.

### Errors
- Canonical status codes used correctly: `INVALID_ARGUMENT` (bad input),
  `FAILED_PRECONDITION` (state does not allow it), `NOT_FOUND`, `ALREADY_EXISTS`,
  `PERMISSION_DENIED`, `UNAUTHENTICATED`, `RESOURCE_EXHAUSTED` (quota),
  `UNAVAILABLE` (retryable).
- Rich error details (`google.rpc.BadRequest`, `ErrorInfo`) for field-level problems.
- Retry policy documented: which codes are safe to retry for which methods.

### Packaging and tooling
- Package names versioned (`acme.orders.v1`); breaking changes go to `v2` packages.
- `buf lint` with the team's rule set (`STANDARD` is a sensible default) and
  `buf breaking` against the main branch in CI.
- JSON transcoding (gRPC-Gateway, Envoy) considered: field names map to lowerCamelCase
  JSON; check that this matches public REST conventions if exposed.

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc