API Contract Reviewer
Reviews contracts — REST OpenAPI specs and protobuf .proto files — for stability, versioning hygiene, and completeness. Catches the contract issues architects encoded rules against before they ship to clients.
1. When to invoke
- A PR changes
.protofiles, OpenAPI YAML, or any handler that affects the wire contract. - Before publishing a new major version of an API.
- Before deprecating a field or method.
- Periodic audit of an existing API for drift between spec and implementation.
2. Output format
Same shape as security-reviewer §2 — findings table with severity, then a one-line summary.
| Severity | Rule | Location | Evidence | Fix |
|---|---|---|---|---|
| Critical | Breaking change to v1 | `proto/orders/v1/order.proto:23` | Field `status` type changed `string` → `int32` | Revert; introduce `status_v2` as a new field in v1, deprecate `status`, or bump to v2 |
| High | Versioning mismatch | `openapi.yaml` | Endpoint `/orders` lacks `/v1/` prefix | Add `/v1/` prefix per rest-api-architect §4 |
| Medium | Missing OpenAPI example | `openapi.yaml:42` | `CreateOrderRequest` has no `example:` | Add a realistic example — drives SDK gen + docs |
| Low | Inconsistent error shape | `openapi.yaml` | 404 returns `{detail: ...}` while 422 returns RFC 7807 | Standardize on RFC 7807 per rest-api-architect §7 |
Severity guide:
- Critical — wire-breaking change in a stable version (clients break on deploy).
- High — convention violation that's expensive to fix later (versioning, error shape).
- Medium — completeness gap that hurts client experience (missing examples, undocumented errors).
- Low — polish (inconsistent casing, missing descriptions).
3. Review approach
- Mechanical pass (REST:
openapi-diff; gRPC:buf breaking) — surfaces wire-breaking changes. - Convention pass — read the spec / proto files against the architect rules in §4.
- Completeness pass — every endpoint, every model, every error has the metadata clients need (examples, descriptions, types).
4. What to check — by category
Versioning
Per rest-api-architect §4 and protobuf-architect §4:
- REST: every endpoint under
/v1/,/v2/, etc. Not in headers, not in query params. - gRPC / proto: version is part of the package path (
acme.shop.orders.v1), never appended to message names (OrderV1is wrong). - Additive changes don't bump the version — new optional field, new endpoint, new enum value, new RPC method. Stay in the existing version.
- Breaking changes always bump the major version — field removal, type change, semantic change, required-field tightening. New
vNpackage + run side-by-side. - Deprecate before remove — add
[deprecated = true](proto) ordeprecated: true(OpenAPI), setDeprecation/Sunsetheaders (RFC 9745 / 8594) for REST.
Field & method hygiene (proto)
- Field numbers never reused. Removed field →
reserved N;+reserved "name";. - Field type never changed.
int32 → stringis wire-breaking even if the runtime value fits both. - Field numbers 1–15 reserved for fields read on every request (1-byte wire encoding).
- Enum:
*_UNSPECIFIED = 0mandatory. Enum value namesUPPER_SNAKE_CASEprefixed with the enum name. - No primitive wrappers (
StringValue,Int32Value) — useoptionalinstead.
Error contracts
Per rest-api-architect §7 for REST and grpc-architect §2 for gRPC:
- REST: every error returns
application/problem+json(RFC 7807) —type,title,status,detail,instance,correlation_id. Never{"detail": "..."}and{"errors": [...]}mixed in one API. - gRPC:
status.Errorwith a standard code. Domain-error → code mapping is centralized; no handler invents its own. typeURLs are stable once published — clients switch on them.- 422 validation errors include the structured field list per rest-api-architect §7.
- 5xx responses always include
correlation_idties to server logs.
Idempotency and concurrency
Per rest-api-architect §8 and §9:
Idempotency-Keymandatory on POST/PATCH. Missing →400. Documented in OpenAPI as a required header.ETag+If-Matchmandatory on PUT/PATCH. Stale →412. Documented.- Cursor pagination, not offset. Documented
next_cursorandlimitin response.
JSON shape & encoding
snake_caseJSON field names.- ISO 8601 timestamps, string-encoded with timezone.
- Money as strings (
"99.99"), never JSON numbers. - UUIDs as canonical hex with dashes, UUID v7 preferred.
null≠ missing — both are documented behaviors in PATCH.
OpenAPI completeness (REST)
- Tag, summary, description on every operation. They drive docs and SDK code-gen.
responses:documents non-default codes (401,403,404,409,412,422).examples:on every request / response model. SDKs render them; integration tests use them.requestBody.required: truewhen the body is mandatory — default isfalse, easy to miss.securitySchemesdeclared (Bearer / OAuth2) and referenced per-endpoint.servers:andinfo.contactset — these aren't FastAPI defaults but matter for published specs.info.versionmatches the API major version (1.0.0, not0.1.7).
gRPC service hygiene
Per grpc-architect §1:
- One service per file.
- Every RPC takes a
<Verb><Noun>Requestand returns<Verb><Noun>Response. Nevergoogle.protobuf.Emptyas input. ListXRequest/ListXResponseuse cursor pagination matching REST conventions.google.protobuf.Emptyonly for fire-and-forget responses with no useful return.- Streaming pattern justified in proto comments (server-stream vs client-stream vs bidi).
Documentation drift
The spec is the contract; drift between code and spec is a contract failure:
- OpenAPI generated from code, not hand-written. Per rest-api-architect §15.
- Snapshot test in CI: the spec is asserted against a committed snapshot file. Any change is reviewed.
- gRPC equivalent: generated code is committed under
gen/per protobuf-architect §6. PR shows the generated diff alongside the proto diff.
5. Tooling
Run these on the diff before the read pass; their output goes into the report.
| Tool | Catches |
|---|---|
buf breaking --against '.git#branch=main,subdir=proto' | Wire-breaking changes in .proto files |
buf lint | proto3 style + Buf-style package naming |
openapi-diff <old> <new> | Wire-breaking changes in OpenAPI specs (additions, removals, type changes) |
swagger-cli validate openapi.yaml (or redocly lint) | OpenAPI 3.1 validity + completeness rules |
| Snapshot diff in CI | assert(app.openapi() == snapshot) per fastapi-architect §10 |
These run in CI per [rest-api-architect §15](../../protocols/rest-api-architect/SKILL.md#1