authoring-api-spec — SKILL.md
Variant: standard · When to use: authoring the engineering wire contract of an API surface — to a bar where a client integrates and a server implements from the contract alone, with no ambiguity about shapes, errors, or auth.
Overview
This skill is the how-to of writing a strong API specification — the engineering wire contract of an API surface. It says what each operation looks like over the wire: the request and response shapes (fields, types, required/optional, constraints), the auth model, the error model, and worked examples. This skill carries the producer's judgment — the research method and the quality bar — not the section list. It assumes two collaborators: an api-spec template tool that supplies the section structure, and a deep-research capability to ground the contract in established API-design practice. The producer is handed the upstream feature-spec (the behaviors the API must expose) and, where present, the architecture-doc (service boundaries) and the data-model (the entities resources map to) — never a blank page. The bar to clear: the contract is implementable and callable — a client engineer can call every operation correctly and a server engineer can implement it without asking the author, with no ambiguity about shapes, errors, or auth.
When to activate
- Authoring an API specification from an approved feature-spec that names the behaviors an API must expose.
- Specifying the operations, request/response schemas, auth, error model, and examples of an API surface (REST, GraphQL, or RPC/gRPC).
- Filling an api-spec template with researched, decision-complete, fully-typed per-operation content.
- Amending an approved api-spec as a versioned delta (a new/changed operation, a field added/removed, a parameter made required, an operation retired) — see Step 6.
Do NOT activate when:
- Designing the persistence data-model (stored entities + relationships) → that is a separate concern and is upstream here. The api-spec describes wire DTOs and references the data-model; a DTO is not a stored row.
- Writing the published, consumer-facing API reference (the prose docs end-users read, often generated from this contract) → that is a separate downstream document with a different audience.
- Writing the implementation / architecture (how the service behind the endpoints is built) → that is the design layer, not the interface.
- Reviewing or grading a finished api-spec → use
reviewing-api-spec(the dedicated gate, single-sourced with this skill's Step-7 bar); this skill is produce-side only. - Authoring the feature-spec itself (the behaviors) → that is upstream input here.
Inputs
Read every document the plan hands you — your depends_on set (the upstream documents discovery determined inform this one) — and trace this document's content back to them. Do not assume a fixed input: the typical upstreams this skill names are method guidance, not a cap on what you receive. Be self-contained — produce the document from whatever context you actually receive; when an expected informing document is absent, proceed on what you have and surface the gap as an explicit assumption, never fabricate to fill it. And use a research capability where one is available (deep-research) to make the document comprehensive and exhaustive, not merely to fill the template.
Capability context (when provided): If a capability_record (a record from capability-map.yaml product_capabilities) is injected by the caller, read it before Step 1. It defines your scope boundary: owns = entities you cover; refs = entities you reference but do not own; publishes/consumes = events you surface; entry_points/exit_points = how users arrive and leave; has_ui/has_api/has_persistence = which surfaces apply. When present, treat it as a hard constraint — do not stray outside the boundary it defines.
Workflow
Step 1: Take the structure from the template tool — don't invent an outline
Get the section structure from your api-spec template tool (comprehensive variant). Do not restate or re-derive a section list here; this skill supplies the method that fills those sections well. If no template is available, obtain a comprehensive api-spec structure (request/forge one, or fall back to the canonical set: API overview, operation list, per-operation request+response, shared data types, auth+authorization, error model, pagination/filtering/rate-limits, examples, open questions), then proceed.
If a capability_record is present: expose entry_points as API operations; include async operations for publishes/consumes events; mark refs fields as read-only cross-capability references.
Step 2: Pick the API style first — then apply the same rigor in that style's notation
A project uses one API style. Choose it, then render the contract in that style's notation — but the rigor (typed schemas, complete errors, auth, examples) is identical across all three:
- REST → OpenAPI/JSON-Schema conventions: endpoint tables (method + path), typed parameters and request bodies, responses keyed by HTTP status, reusable schemas, declared security schemes.
- GraphQL → SDL: typed object/scalar/enum/input types,
Query/Mutation/Subscriptionroot fields, errors in the top-levelerrorsarray. - RPC / gRPC → proto
service/rpcmethod signatures with typed request and response messages and status codes.
Do not mandate OpenAPI for a non-REST project — OpenAPI is the REST flavor, not the only format. The skill teaches contract rigor for whichever style the project uses.
Step 3: Load the upstream docs; drive coverage off the feature-spec
Read the approved feature-spec and any upstream context (architecture-doc, data-model) — this is your input, not a blank page. The feature-spec's behaviors are your operation-coverage checklist: every behavior the API must expose becomes one or more operations; every operation traces back to a behavior (no orphan/invented endpoints). Map each resource onto the data-model's entities by reference — note where the wire DTO differs from the stored entity (computed, derived, or omitted fields); do not duplicate or contradict the data-model. Where the upstream is thin, make the gap an explicit open question or stated assumption, never a silently-invented shape.
Step 4: Research to ground the contract
Use a deep-research pass to ground the contract in established API-design practice — REST/HTTP semantics and status codes, JSON-Schema/OpenAPI rigor, GraphQL SDL, RPC/gRPC, consistent naming, error modeling (e.g. RFC 9457 Problem Details for REST), versioning, and auth patterns — for this product's surface, not "API specs in general." If no research capability is available, do NOT fabricate field names, limits, status codes, or error codes — state them as explicitly-flagged assumptions to validate before build.
Step 5: Apply the per-section method
Fill the template's sections to this method. Collapse a section a thin API doesn't need; size proportionally.
- API overview — state the style + transport, the base URL/endpoint, and a concrete versioning scheme (URL path
/v1is least ambiguous and most common — major-version only per AIP-185; plan versioning from the start). State naming + date/time conventions once, here. - Versioning policy — classify changes + plan retirement (state it day-one). State what counts as a breaking change: additive = a new optional field/operation/enum-value the tolerant-reader client ignores (clients must ignore unknown response fields); breaking = removal/rename/type-narrowing/required-add/default-change/semantic-shift (AIP-180 source/wire/semantic). And state the deprecation → sunset lifecycle for a removal:
Deprecation(RFC 9745, the date it became deprecated) → a migration window →Sunset(RFC 8594, the date it may stop responding;Sunset≥Deprecation) + a published migration path. The policy is stated here; the act of deprecating a specific operation during a change is Step 6 (amend). - Resource / operation list — one row per operation up front (method+path / root field /
Service.Method) so the surface is scannable before the per-operation detail. - Per-operation request + response — fully typed on both sides. Every parameter/argument/field carries a type, a required/optional flag, and constraints (length, enum, format, range). Every response is keyed to a status code (or gRPC status / GraphQL data-vs-errors) with its body schema. Choose the status deliberately —
201+Locationfor a create,204for a no-body success,401(unauthenticated) vs403(forbidden/insufficient-scope) not conflated,409for a conflict,422/400for validation; not200for everything. An operation specified only on the happy path is not done. - Shared data types — define each reusable DTO once and reference it by name (
$ref/ SDL type / proto message); never inline-redefine the same shape across operations. These are wire types — where a DTO projects a stored entity, reference the data-model, do not restate it. - Auth + authorization — name the scheme(s) (API key / OAuth 2.0 + which flow — auth-code+PKCE for user-facing/public clients, client-credentials for service-to-service / JWT / mTLS), where the credential goes, and the authorization model: every non-public operation names its required scope/role, mapped to the operation list's auth-scope column ("authenticated" without a per-operation scope is under-specified). State token lifecycle + HTTPS + no secret in a URL.
- Error model — the catalog, not just the happy path. Define one consistent error shape for the whole API (e.g. RFC 9457 Problem Details for REST; an
errorsarray for GraphQL;google.rpc.Statusfor gRPC) carrying a machine-readable code (the client branches on it — the HTTP status is too coarse), a human message (for logs, not end-user display), and a request/trace id. Then enumerate every named failure case per operation (validation, auth, forbidden, not-found, conflict, rate-limited, server) with its status code and its retryability (429/5xxretryable afterRetry-Afterwith backoff;4xxnot — retrying won't help). This is the single most-skipped part of an api-spec — do it deliberately. - Pagination / filtering / sorting / rate-limits — for collection operations, pick a pagination strategy (cursor-based for large/dynamic/user-facing; offset for small/static), define the request params + the response envelope (next-cursor / has-more /
Linkheader) + default/max page size; list filterable + sortable fields with a stable tie-breaker; state rate limits + the headers + the429behavior. - Worked examples — at least one full request/response pair per primary operation, consistent with the schemas (same field names, types, status codes). An example that contradicts its schema is a defect.
Step 6: Amending an approved contract (the delta case)
When the contract already exists and a change is requested — edit the delta, don't redraw. Scope = the touched operations + their request/response schemas + the shared types + the error cases on them (+ the versioning section if the change is breaking). Then:
- Classify the change — additive vs breaking. Additive (a new optional field, a new operation, a new optional response field a tolerant-reader ignores, a new optional error case) ships in-version. Breaking (a removed/renamed field or operation, a type narrowing, an optional field made required, a default/semantic change, a removed enum value) does not edit in place under the same version. A breaking change shipped as a minor in-version edit silently breaks every live client — the cardinal amend defect.
- A breaking change is a versioning event, not an edit. Route it to a new version (
/v2, a new date-pin) and put the old surface through the deprecation → sunset lifecycle (Deprecation→ a migration window →Sunset,Sunset≥Deprecation) with a migration guide (old→new mapping). Never silently mutate or delete the old operation. - Analyze backward/forward compatibility — backward: the existing version keeps working; forward: new fields optional/defaulted, enum additions tolerated, an operation never silently changing meaning.
- Version + changelog the delta — bump the document's own version (the §10 revision history, distinct from the API version) + a changelog entry (additive/breaking); mark retired operations deprecated, not silently deleted.
- Flag the forward/downward ripple — the change ripples to the downstream api-reference (re-synced/re-generated from the changed contract + the deprecation markers), the implementation + test-plan building against it, the live clients (the reason for the sunset window), and the release-runbook; the upstream feature-spec is amended first (a new behavior drives the new operation). Name the ripple; don't leave it for a downstream reader to discover.
A greenfield first contract skips Step 6 (no prior version to amend).
Step 7: Self-check against the no-ambiguity bar before handing off
Confirm all hold (this is the bar a runtime review gate — reviewing-api-spec — asserts; author and gate share it so they don't drift):
- Style + base + versioning stated — the API style, base URL/endpoint, and a concrete versioning scheme are present; the breaking-vs-non-breaking change rule + the deprecation→sunset policy are stated.
- Every operation listed + traced — the operation list is complete; each operation maps to an upstream feature-spec behavior (no orphan/invented endpoints, none missing).
- Every operation fully typed on both sides — request and response fully typed, each field required/optional with constraints, every response keyed to a status code. A happy-path-only operation fails.
- Auth + per-operation authorization — authentication scheme(s) (+ OAuth flow per client type) stated; every non-public operation names its required scope/role; token lifecycle + HTTPS; nothing implicitly open.
- Error model complete — one consistent error shape (machine code + message + request id) and every named failure case enumerated per operation with its retryability. A spec listing only 2xx responses fails.
- Shared types defined once + referenced — reusable DTOs defined once and referenced; wire types reference the data-model rather than restating/contradicting it.
- Pagination/rate-limits specified — collection operations define pagination; rate limits + headers stated.
- Examples present + consistent — at least one request/response pair per primary operation, matching the schemas exactly.
- Naming + versioning consistent — applied uniformly across the surface.
- Grounded, honest & one-directional — operations/shapes reflect the feature-spec + data-model (not fabricated; gaps surfaced as assumptions/open-questions, not invented); one-directional vs the api-reference (this contract is the published reference's source of truth — never reverse-engineered from it); where a live API exists, claims about current operations/shapes are consistent with the shipped surface (greenfield: N/A).
- (Amend only) delta well-scoped + classified + versioned — on a change request: the changed operations meet 1–10 on what they touched; the change is classified additive/breaking; a breaking change carries a new version + the deprecation→sunset plan + a migration guide; backward/forward compatibility analyzed; the forward/downward ripple flagged; the document version bumped + a changelog; retired operations marked deprecated, not deleted. On a greenfield first build, n/a.
Thin-input gate: if a behavior the API must expose cannot be researched or even credibly assumed into a contract, surface it as a blocker ("operation under-defined — needs product/engineering decision") rather than inventing a shape. An api-spec whose shapes, errors, and auth are all guesses is not callable.
Rules
Hard rules (never violate):
- Type both sides of every operation. Request and response fully typed, each field required/optional with constraints, every response keyed to a status code. An untyped field or an unstated status is not done.
- Enumerate the error cases. Every operation names its failure cases (validation, auth, not-found, conflict, rate-limit, server) with status codes — not just the happy path. A happy-path-only operation is incomplete.
- One consistent error shape. The whole API shares a single error response shape (machine code + message + request id); don't invent a different error body per endpoint.
- Reference the data-model, don't redefine it. Wire DTOs map onto the upstream data-model's entities by reference; note the deltas. Never restate or contradict the stored schema. A DTO is not a stored row.
- One-directional vs the api-reference. The published consumer-facing api-reference is the downstream consumer of this contract — often generated from it. This contract is the reference's source of truth; never reverse-engineer the contract from the published docs (that inverts the dependency). The data-model is upstream, the api-reference is downstream.
- A breaking change is a new version, not an in-place edit. When amending, classify additive vs breaking; route a breaking change to a new version + a
Deprecation→Sunsetlifecycle with a migration path; never silently mutate or delete a live operation under the same version (Step 6). - Trace every operation to a feature-spec behavior. No orphan/invented operation, no missing behavior. Coverage runs off the feature-spec.
- Style-agnostic rigor, not OpenAPI-only. Render in the project's one style's notation (OpenAPI for REST, SDL for GraphQL, proto for RPC); apply the same rigor regardless. Don't force OpenAPI onto a non-REST API.
- Examples match the schemas. Every example uses the exact field names, types, and status codes the schemas declare. An example contradicting its schema is a defect.
- Never fabricate. Don't invent field names, limits, status codes, or error codes to look complete. With no source, state them as explicitly-flagged assumptions to validate before build.
- Compose, don't duplicate. Take the section structure from the template tool; this skill is the method that fills it. Do not paste a competing outline.
- Contract, not implementation, not published reference. Specify the interface (the wire shapes), not how the service is built and not the prose end-user reference.
- Capability boundary (when capability_record provided). Scope the document output to that record's boundary. Producing content outside the boundary (covering a
refsentity as if it were owned; designing flows past anexit_point) is a scope violation — equivalent to inventing content that isn't in the spec.
Preferences (override-able):
- "Comprehensive" sets output ambition; stay proportional — completeness of the contract (every operation typed, errors enumerated, auth stated), not word count. A thin API collapses sections it doesn't need.
- Prefer RFC 9457 Problem Details for REST error bodies; prefer cursor-based pagination for large/dynamic collections.
- Prefer URL-path versioning (
/v1) unless the project already standardizes on header/date-based.
Gotchas
- Happy-path-only operations. Specifying the
200body and stopping is the most common api-spec defect. Each operation must enumerate its4xx/5xxfailure cases with status codes — the error catalog is part of the contract, not an afterthought. - Redefining the data-model as DTOs. Re-typing the stored entity inline drifts from (and contradicts) the upstream data-model. Reference it; note only where the wire shape differs (computed/derived/omitted fields).
- Confusing the contract with the published reference. This is the engineering contract the build implements against; the consumer-facing reference (often generated from it) is a separate downstream document with a different audience. Don't write prose end-user docs here.
- OpenAPI tunnel vision. Reaching for OpenAPI on a GraphQL or gRPC API misfits the style. Pick the project's one style; render in its notation (SDL / proto).
- Examples that drift from the schema. An example with a field the schema doesn't declare (or a wrong type/status) breaks the contract silently. Keep examples consistent with the declared shapes.
- Restating the template outline. Re-deriving the section list inside the content (or in this skill) duplicates the template tool and drifts — fill its sections with judgment instead.
Worked contrast — under-specified (compliant on the surface) vs callable (use it to self-detect):
| Aspect | Under-specified (reject) | Callable (ship) |
|---|---|---|
| Operation trace | "There's an endpoint for invoices." | "POST /v1/invoices implements feature-spec §3.2 Create invoice; projects the Invoice data-model entity (omits internalLedgerId)." |
| Request | "Takes invoice fields." | "Body: amount (integer, required, >0, minor units), currency (string, required, ISO-4217), dueDate (string, optional, RFC-3339)." |
| Response | "Returns the invoice." | "201 → Invoice schema (section 4); Location header set to the new resource." |
| Error model | "Errors are returned." | "400 validation_error (bad field), 401 unauthorized, 403 forbidden (missing invoices:write), 409 conflict (duplicate idempotency-key) — all in the shared Problem-Details shape." |
| Auth | "Authenticated." | "OAuth 2.0 bearer; requires scope invoices:write." |
| Example | "See the docs." | "Request/response pair using amount: 5000, currency: \"USD\"; 201 body matches the Invoice schema exactly." |
If your fill reads like the left column — true of any API, no types, no status codes, no error cases — it isn't done.
Anti-patterns
- "I'll spec the happy path; the errors are obvious." They aren't — name each failure case with its status code, or a client can't handle it.
- "I'll redefine the entities as request/response shapes." Reference the data-model; restating it drifts and contradicts. Note only the wire deltas.
- "OpenAPI is the standard, I'll use it for everything." OpenAPI is the REST flavor; a GraphQL API needs SDL, a gRPC API needs proto. Match the project's style.
- "This is also the API reference, so I'll write end-user prose." The contract and the published reference are different documents/audiences; keep this the engineering contract.
- "I'll write the outline myself." Duplicates the template tool — take the structure from it.
- "Skip the research, I know REST." The research grounds this product's surface, naming, and error model — not API theory.
- "It's a small change, I'll edit the field in place." A breaking change (rename/remove/type-narrow/required-add) under the same version silently breaks every live client — classify it, route it to a new version + a
Deprecation→Sunsetlifecycle with a migration guide (Step 6).
Output
A comprehensive API specification that meets the Step 7 no-ambiguity bar (style+base+versioning [+ breaking-change rule + deprecation/sunset policy] stated; every operation listed, traced, and fully typed on both sides with status codes; auth + per-operation authorization specified; a complete error model with every failure case named + retryability; shared types defined once and referencing the data-model; pagination/rate-limits specified; examples present and consistent; naming/versioning consistent; grounded + one-directional vs the api-reference; nothing fabricated) — and, on a change, amended as a versioned delta (Step 6: classified additive/breaking, a breaking change routed to a new version + deprecation→sunset, the ripple flagged). The artifact is textual — endpoint tables + fenced schema blocks (JSON / OpenAPI fragment / SDL / proto) + example request/response pairs; the method + bar are medium-independent. The abstract consumer is the client engineers who integrate against the contract, the server engineers who implement it, the downstream published api-reference often generated from it, and the reviewing-api-spec gate (which asserts the same bar). The api-spec depends on the feature-spec (and references the data-model) as input. Its structure comes from the template tool; this skill supplies the content quality.
Related
- An api-spec template tool (e.g. a content/template gateway) — supplies the section structure this skill fills. Compose with it; never restate its outline.
- A deep-research capability — grounds the contract in established API-design practice (REST/HTTP, OpenAPI/JSON-Schema, GraphQL SDL, gRPC, error modeling, versioning, auth).
- A feature-spec-authoring skill — produces the upstream feature-spec whose behaviors this contract exposes.
- An architecture-doc and a data-model (where present) — upstream context: the service boundaries, and the entities the wire DTOs reference.
reviewing-api-spec— the dedicated review gate for the finished api-spec; asserts the same no-ambiguity bar (its conditions are single-sourced 1:1 with this skill's Step-7 self-check) so author and gate don't drift. (The genericdesign-reviewcarves the api-spec artifact out to this twin.)- A consumer-facing API-reference skill — the downstream published reference, a distinct document often generated from this contract.
Progressive disclosure
references/contract-method.md— depth on the contract spine: style choice + operations/method-semantics (RFC 9110) + status-code selection + type-both-sides + the complete error model (RFC 9457 + machine code + retryability) + auth/per-operation-authorization + pagination/tie-breaker. Load when filling a section needs the worked detail.references/versioning-and-amend.md— depth on versioning + evolution: the scheme (URL-path/header/date-based, AIP-185), the breaking-vs-non-breaking classification (AIP-180), the deprecation→sunset lifecycle (RFC 9745/8594), and the Step-6 amend method + the forward/downward ripple. Load when versioning or amending.references/sources.md— research provenance for the method + quality bar (OpenAPI, JSON Schema, GraphQL SDL, gRPC/protobuf, RFC 9110/9457, RFC 8594/9745, AIP-180/185, REST design + error/pagination guides). Load only to audit where the guidance came from.
Body budget
description≤ 1,024 chars (agentskills.io cap).- Body ~500 lines / 5,000 tokens (soft target — quality takes precedence; flag if consistently over 700 lines / 7,000 tokens).
- Heavy content lives in
references/, loaded on demand.
Changelog
- 1.3.0 (2026-06-21) — capability_record context injection: optional caller-injected
capability_recordconsumed in Step 1 (scope boundary fromentry_points/publishes/consumes/refs);capability_boundaryrule added; body budget softened to target. Graceful fallback when no record injected. - 1.2.0 (2026-06-15) — restructure for the production-grade pair (single-sourced with the net-new
reviewing-api-spectwin). Added the amend method (Step 6: classify additive/breaking → a breaking change is a new version + aDeprecation→Sunsetlifecycle → compatibility → version+changelog → the forward/downward ripple to the api-reference/impl/clients). Deepened versioning (breaking-vs-non-breaking classification + the deprecation→sunset policy, AIP-180/185 + RFC 8594/9745), per-operation authorization (every non-public operation names its scope + the OAuth flow per client type), the error model (machine code + request id + retryability), status-code selection, and the one-directional-vs-the-api-reference rule. Extended the self-check to Step 7 (cond-1/4/5/10 deepened + a new cond-11 amend) — single-sourced 1:1 with the twin's 11 conditions. Re-pointed the gate references atreviewing-api-spec. Addedreferences/contract-method.md+references/versioning-and-amend.md. No prior coverage dropped. - 1.1.0 (2026-06-04) — initial reviewed release.