api-contract-openapi
Use when you add or change any HTTP endpoint's request or response shape (Go or Java) — implement the task's contract exactly, evolve additively, keep the OpenAPI document in sync, and detect breaking
SKILL.mdcatalog/agents/backend-developer/skills/api-contract-openapi/SKILL.md
GitHubOpenAPI & API Contracts
Overview
The API contract is a promise the web and mobile clients depend on. A silent shape change (renamed field, changed type, removed endpoint) compiles fine on the backend and breaks every consumer at runtime.
Core principle: the contract is the annotation + DTO in code. Implement the task's contract exactly, evolve it additively, and never edit a consumer yourself.
Your task's contract is already copied into the consumer tasks
Your task's Interfaces / contract block is already copied, word for word, into the consumer tasks (the architect writes it once and copies it to the frontend and mobile tasks, which are blocked_by and deploy_depends_on yours). Implement it exactly: path, method, field names and casing, types, status codes. A shape you think is better is a comment on your task, not a change.
Consumers are not yours to edit. Find them with list_links (direction in) and grep_code for the path or field. Another component's or repository's client code belongs to its own task, and cross-repository actions are refused. Keep your change additive. When a breaking change is truly required, add_task_comment naming every consumer task or component and what each must change, and stop short of the break.
Detect the spec source
| Signal | Tooling |
|---|---|
//@Summary///@Router comments |
swaggo (swag init; v1 emits Swagger 2.0 only) |
openapi.yaml/.json + generated server stubs |
oapi-codegen (spec-first, go generate ./...) |
| Go struct tags driving a schema, no annotations | huma or ogen |
@Operation/@Schema on JAX-RS resources |
springdoc-openapi / quarkus-smallrye-openapi — spec is generated at runtime; snapshot it at /q/openapi or /v3/api-docs |
A hand-maintained openapi.yaml with no generator |
edit it directly |
Keep the document's existing OpenAPI version (3.0/3.1/3.2 all current); upgrading the spec version is never part of a feature task.
Additive vs breaking
| Change | Type | Action |
|---|---|---|
| Add optional field | Additive | Safe; document it |
| Add required request field | Breaking | Coordinate via the task's contract, never edit callers yourself |
| Rename field | Breaking | Prefer add-new + deprecate-old |
| Change field type | Breaking | New field or coordinated cutover |
| Remove endpoint/field | Breaking | Deprecate first, remove later |
Breaking-change check
Diff the spec against the merge base before you finish:
mkdir -p /tmp/tt-<task key> && git show "$(git merge-base HEAD origin/HEAD)":api/openapi.yaml > /tmp/tt-<task key>/base.yaml
go run github.com/oasdiff/oasdiff@latest breaking /tmp/tt-<task key>/base.yaml api/openapi.yaml
It must print nothing unless the task is the planned break. Lint with npx --yes @redocly/cli lint api/openapi.yaml when the repo has no linter already wired in.
Every endpoint documented
Request/response schema, every status code it can return (including the error shape — see api-design-conventions), and pagination parameters where the endpoint lists anything.
Worked Example
Task's contract: POST /tasks returns {id, title, status}. A later task wants to rename title to name for a different repository's consumer.
Wrong: rename the DTO field and ship — every client reading title breaks, and your task has no authority to touch their code.
Right (additive cutover):
- Add
namealongsidetitlein the response; populate both. Documenttitleas deprecated in the spec. add_task_commentnaming the consumer task(s)/component(s) that must switch toname.- In a later task — once the architect confirms no consumer reads
title— remove it with a contract note.
Each step keeps every consumer green and respects task boundaries.
Common Mistakes
- Implementing a shape you think is cleaner instead of the task's Interfaces block verbatim.
- Editing a consumer's client code because it was easy to find.
- A required new request field added with no coordinated consumer task.
- Spec left stale after a handler change, or
oasdiffnever run.
Red Flags
- A DTO field renamed or removed with
oasdiff breakingprinting output. - A diff that touches files outside your component/repository to "fix" a client.
- A consumer reading a field the backend just removed, with no deprecation step.