Writes the OpenAPI 3.0 contract from the SRS: operations, schemas, problem+json errors, security. Use for «опиши API», «какие эндпоинты нужны», «спецификация API». Endpoint code: work.
Resources
2Install
npx skillscat add idontlikedrinkpivo/web-dev-pipeline-codex-plugin/openapi-spec-generator Install via the SkillsCat registry.
Codex runtime. Before using this skill, read
../executor-catalog/references/codex-runtime.md. It defines tool mappings, named-agent dispatch and stage-chat orchestration; use it for inherited Claude Code terms below.
OpenAPI 3.0 Specification Generator
Orchestrator (session)
The session writes no OpenAPI spec. The dispatched writer settles it; a
writer on Opus nests doc-typist to print it, design-lite prints it
itself (executor-catalog → Nesting). A Mid or High report whoseDELEGATED is not doc-typist comes back until it is. The session
dispatches the typist only for a print defect or a check's lines (step 4).
- Stop if the SRS is missing. Name
srs-writer. If the slice persists
state and there is no schema document, stop and namedb-schema-design.
Do not wait forclean-architecture-design. Fromupgrade-docs
(MODE as-is,upgrade-docs/references/as-is.md) the routes stand in
for a missing SRS or schema: every route the app serves is an operation,
and none of the questions below is asked — the code answers them. - When a spec already exists, read
doc-versioning.
The writer runs as a subagent and cannot ask the user. Before dispatch, settle what the SRS leaves open: the auth scheme when the
security NFR names none (no answer → rootsecurity: [], Step 4),
the status of each named use-case exception the SRS gives none, and —
on a new spec, when the SRS does not say — whether anyone outside the
project calls the API (Step 2, Contract version).
One question per message (pipeline/references/question-format.md);
each answer goes into the packet's ANSWERS.
A single-file spec is split here, before dispatch (writers and the
typist do not commit): runpython3 "<folder of this SKILL.md>/scripts/split_spec.py" .— it splits with
redocly into the layout of Step 8, bundles the old file and the new
root, and moves the files in only when the two bundles match, since the
split must change no operation, schema, version or changelog row. Exit
0: commit the move on its own («docs: OpenAPI разложен по файлам»); it is
a move, not a version event, and the change that follows is committed by
the stamp when the stage closes. Exit 1: the bundles differ — show the
lines it printed and stop. Exit 3 (no Node or network): split by hand
into the same layout — each path item and each component moved into its
file and replaced in the root by its$ref, nothing else touched — say
in the close that the bundle comparison did not run, and commit the same
way. - Read
executor-catalogandexecutor-catalog/references/design-complexity.md. Dispatch
exactly one writer withexecutor-catalog/references/design-writer-prompt.md. Before theAgentcall, writeGrade | Executor | subagent_typefor the design
row the grade picked, then dispatch it without asking which model.
Follow the catalog dispatch contract:subagent_typeis<row>
and the call passes nomodel(the agent file holds it) unless the
user named one. A missing agent or a rejected alias is a stop. - Inspect
documentation/api/openapi.yamlon disk. Runnpx --yes @redocly/cli lint documentation/api/openapi.yaml(orspectral lintwhen
the repo already has a.spectral.yaml), until the lint is clean. Anerrorwhose fix you can state as a finished line (a missingdescription, a broken$refpath, a stray key) is a print defect:doc-typistMODE revise, oneSETTLEMENTline per error with its
JSON pointer; one or two such errors you fix yourself, a dispatch costs
more (executor-catalog→ Nesting). An error whose fix needs a
decision (a missing response, a schema shape) sends the writer back
with the rule ids and pointers. When no linter can run
(no Node, no network), say in the stage close that the spec was not
linted. Do not edit the spec yourself. ABLOCKEDwith a question for
the user is asked and sent back to the same row; any otherBLOCKED/HARDER_THAN_EXPECTEDescalates one design row (executor-catalog→
Escalation); every round goes on while it makes progress (pipeline→references/convergence.md). - Close the stage by asking, per
pipeline→ "Asking before a
transition". First ask aboutdoc-review. Run: run it; it returns
without asking the next stage. Skip: name it in the report. Stop:
stop. After a run or a skip, ask the one next stage the table names. The writer does not run
those.doc-reviewedits the body and does not bumpinfo.version. Seedoc-versioning.
Writer (dispatched)
You settle the contract, then print it by the packet's PRINT
(executor-catalog → Nesting, packetexecutor-catalog/references/doc-typist-prompt.md): doc-typist nested,
or yourself as design-lite. A gap this skill calls BLOCKED returns
before printing. Read the file back. With the typist: one correction
dispatch, then the report; do not type the correction, and do not paste
the finished spec into its prompt.
MODE as-is (upgrade-docs/references/as-is.md §5): paths, statuses and error bodies come from the code; Step 3's
problem+json rule, Step 6's status list and references/base-schemas.md do not apply.
Translate already-decided use cases into an HTTP contract. The algorithm
lives in the SRS use case. This skill only names the route, the
request/response schemas, and the status codes. info.description, operation summary /description, and schema description / example are Russian. Do not
translate operationId, paths, or schema names. A description is written
for the one who calls the API — what the operation does, who may call it, what to send, what comes back, each
fact once, in plain sentences; why an error comes lives in that response; how the server does it lives in the
architecture: references/descriptions.md. They say what a field or
an operation means, never how a client shows it: no «для выпадающего
списка», «показывается в модалке», colour or icon fields the SRS does not
require as data (doc-versioning → references/writing-rules.md → What, not how it looks).
Workflow
Step 1 — Gather Context
Read the SRS. Do not invent an endpoint that has no use case behind it.
A screen read that is not a use case is not this stage's to add; design
sends that gap back.
The DB schema comes first when the system has storage. db-schema-design
runs before this skill, and its column names and types are the contract this
spec must agree with — a schema written afterwards against an already-published
spec has to either rename columns or carry a translation layer nobody asked
for. If storage exists but no schema document does, return STATUS BLOCKED
naming db-schema-design; the one
exception is a storage-free surface (pure computation, proxy, webhook relay),
where there is nothing to agree with.
Infer from the inputs. You cannot ask the user; a gap that needs an
answer and is not in the packet is STATUS BLOCKED with the question:
| Question | Why it matters |
|---|---|
| Which use cases / queries are in scope? | One operation per use case or query |
| Authentication type(s)? | securitySchemes. The SRS security NFR, or the answer in ANSWERS. What to emit — Step 4 |
| Existing partial spec to extend? | Merge rather than overwrite |
Map from the SRS and the schema:
| Source | OpenAPI |
|---|---|
| One use case | One operation (operationId = camelCase of that use-case verb) |
| What the use case accepts | Request body and/or path/query params |
| What the use case returns on success | Success response schema. Do not invent a body the use case did not name |
| An exception whose status the SRS names | That status. Do not re-pick 409 vs 422 |
| An exception with no status | The status in ANSWERS; if none, return BLOCKED with the question. Do not pick a status in silence |
| Schema column | Property name and type. Do not rename — except the keys, by the schema's rule (db-schema-design → How the API names keys): public_id is the resource's id, a reference <thing>_id keeps its name and carries the referenced row's public_id (uuid), a path parameter is <resource>_id; the internal bigint key never appears. A closed-set column's enum is the code list from the schema's ## Закрытые наборы значений, verbatim |
| SRS security NFR | securitySchemes and root security (Step 4) |
If a DBML schema (documentation/db/schema.md) exists, every stored property in a request or
response body must exist on the corresponding table (or be a value the
use case says is computed). Flag mismatches instead of inventing a
second name.
Step 1b — Existing spec: extend it under versioning rules
The spec arrives split into files (Step 8 → Layout): the session splits a
single-file spec and commits the move before dispatch (Orchestrator,
step 2). A single-file spec in the packet returns BLOCKED naming that
step.
An existing documentation/api/openapi.yaml is the canonical contract, not a draft to
replace. Read the doc-versioning skill and follow it, plus what is specific
to a live HTTP contract. This invocation edits the spec in place and does
not touch info.version or info.x-changelog; the stamping commit
(doc-versioning) writes the number (Step 2, Contract version, says which)
and the typed rows. The report's DOWNSTREAM line names each change with
the type it implies, so the stamp can type it:
- A
description/summary/exampleon an existing operation or
property is no change — nothing to stamp. A new operation, path, schema,
or optional property isдобавляет. A removed or renamedoperationId,
path, method, schema name, or property, a property that became required,
or a changed type, status, or error format isломает. Wording of a
cited contract sentence without a shape change isуточняет. - A breaking change follows who calls the API. Only the project's own
client: theломаетchange ships in the release whose client follows it —
front and back change together. External consumers: never a shape change
under the same path — the change goes into a new major path beside the
old one (Contract version). Deployed partner clients are the reason that
rule has no exceptions. - Reuse the existing
components/schemasandcomponents/responsesinstead of
introducing a parallel name for the same concept. - Deprecate a retired operation (
deprecated: true) and keep it until no client
calls it — with only the project's own client, the release where that
client stops calling it; with external consumers, the old major's
announced sunset. Do not delete it in the same change that replaces it. - Do not add a row to the architecture journal, the schema journal, or the
SRS journal because the spec changed. The report's DOWNSTREAM line also
names the scenarios, the screen specs, generated clients, and the plan.
Do not add a schema changelog row for an operation that changed no
column.
Step 2 — Build the Spec
Always produce a complete, valid OpenAPI 3.0 spec — never leave
placeholder comments like # TODO: add schema. The version is 3.0.3,
not 3.1.0: the Swagger Viewer preview reads only OpenAPI 3.0 and stops
on 3.1 with a blank page, while the file itself stays valid. Do not
raise the version to make a 3.1 keyword available.
info.x-sources is this file's sources: — YAML has no frontmatter, so
the pins live in info, next to info.x-reviewed (set by doc-review; do not
write it). Pin the SRS and, when
the schema stage ran, the schema, each as path@<version> — the version
it was built from. Without them nobody can see that the API went stale
after an SRS or schema change. On an increment, move a pin only when this
pass re-read that source's new version.
Contract version (info.version). It follows who calls the API:
- Only the project's own client (the default): no separate number.
info.versionis the service version — the open iteration's version on
a new spec (on greenfield the first iteration's number, usually0.1.0), afterwards stamped like any
versioned document. No version in the path. - External consumers — the SRS names partners or third-party systems
that call the API, or the architecture foundation lists the Full
trigger "versioned partner API": the contract has its own SemVer ininfo.version, independent of the service version (service 1.3.0 may
bring API v2), and its major in the path (servers: - url: /v1). A new
contract starts at1.0.0under/v1. A breaking change ships only as
a new major path (/v2) beside the old one; the old one stays, its
operationsdeprecated: true, until its announced sunset. An addition
raises the contract's minor inside the current major; a fix its patch.references/common-patterns.md→ Versioning Patterns shows the shape.
Either way a new spec carries the one «первый выпуск» row ininfo.x-changelog, in the form doc-versioning → Changelog gives.
openapi: "3.0.3"
info:
title: <API Title>
version: "0.1.0" # the service version; a partner API has its own SemVer (Contract version)
description: <Краткое описание на русском>
x-sources: # what this contract was built from, pinned by version
- documentation/requirements/srs/srs.md@0.1.0
- documentation/db/schema.md@0.1.0 # only when the schema stage ran
servers:
- url: / # relative; a host appears only when the SRS or the user named it
security: [] # the SRS named no scheme, so no 401/403 below; otherwise the named scheme (Step 4)
tags:
- name: <Tag>
description: <Tag description>
paths:
/resource:
get:
summary: List resources
operationId: listResources
tags: [<Tag>]
parameters: []
responses:
"200":
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/ResourcePage"
example:
items: []
next_cursor: null
"500":
$ref: "#/components/responses/InternalError"
components:
schemas: {}
responses:
InternalError:
description: Internal server error
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
securitySchemes: {}Do not emit example.com hosts or e-mails, and no contact the inputs did
not name: a placeholder host copied from this skeleton ships as the
contract.
Step 3 — Schemas and Models
- Always use
$reffor any schema used in more than one place. - Include
exampleorexampleson every request and response body. - Mark required fields with the
requiredarray. - Use
nullable: trueon a singletypefor a field that may be absent as JSON null. Do not usetype: [string, "null"]: that is OpenAPI 3.1, and this spec is 3.0. - Prefer
formatkeywords:int32,int64,float,date,date-time,uuid,email,uri,byte,binary. - Property casing is the column casing. A stored property is the column
name verbatim (snake_case, e.g.starts_at). Wrapper, computed, and query
and path parameter names use the same casing (page_size,next_cursor,user_id). Do not mix camelCase and snake_case property names in one
spec;operationIdstays camelCase.
Base schemas. Read references/base-schemas.md before writingcomponents: the page, the error body (Problem), the timestamps mixin
and the shared responses, each copied as it stands. One copy of each
lives there, so every spec this skill writes has the same error and page
shape the stack skills implement.
Every 4xx and 5xx response uses application/problem+json with Problem.
A named use-case error sets code to the SRS exception name and type to/problems/<code in kebab-case>; do not add a second error schema. When an existing spec
already uses another error format, keep it (Step 1b) — changing it is aломает change, not a silent migration.
Step 4 — Security Schemes
Read references/security-schemes.md for the scheme the SRS named.
Quick reference:
| Scheme | OAS 3.0 type | Notes |
|---|---|---|
| Bearer JWT | http, scheme bearer |
Only when the SRS named JWT |
| API Key (header) | apiKey, in header |
e.g. X-API-Key |
| OAuth 2 | oauth2 |
Use flows to define grant types |
| OpenID Connect | openIdConnect |
Provide openIdConnectUrl |
Apply security globally at the root and override per-operation only
where it differs (public endpoints use security: []).
If the SRS names no scheme, set
root security: []. Do not omit the security key — an omitted key is
not the same as an empty array — and do not invent Bearer JWT /securitySchemes to fill the gap: a guessed scheme becomes the contract
every client implements.
Do not emit API keys in query strings, OAuth password flow, or implicit flow.
If the spec defines any callbacks: entry, add signature verification —
see "Webhook signature verification" in references/common-patterns.md. A
webhook with no verifiable sender is an open write endpoint with no auth.
Step 5 — Parameters
Path parameters — always required: true:
parameters:
- name: user_id
in: path
required: true
schema:
type: string
format: uuid
example: 123e4567-e89b-12d3-a456-426614174000Query parameters — document defaults and enums:
- name: status
in: query
schema:
type: string
enum: [active, inactive, pending]
default: activeHeaders — only when the SRS or the architecture names them, declareX-Request-ID and other correlation ids as common
parameters under components/parameters.
Step 6 — Response Codes
Status vocabulary — pick per operation, never copy the whole table:
| Code | When |
|---|---|
200 |
Successful GET, PUT, PATCH |
201 |
Successful POST that creates a resource |
204 |
Successful DELETE (no body) |
400 |
The request cannot be parsed at all: malformed JSON, an undecodable or tampered cursor |
401 |
Missing or invalid auth |
403 |
Authenticated but not authorized |
404 |
Resource not found |
409 |
Only as a named use-case error (below) |
422 |
A body, query, or path field fails its schema (code: VALIDATION_ERROR with errors), or a named use-case error the SRS sets to 422 |
429 |
Rate limited |
500 |
Internal server error |
503 |
A write lost to a concurrent transaction (serialization failure, deadlock) after the server's own retries; Retry-After says when to repeat |
Per operation emit exactly: its success code; one response per named
use-case error; 422 (VALIDATION_ERROR) when it takes a body or
parameters; 400 only when it takes a JSON body or a cursor; 401 and403 only when the operation is secured; 404 only when the path names a
resource id; 429 only when the SRS names a rate limit; 503 when it
changes stored data in a transaction; 500 always. 409 appears only as
a named use-case error: a uniqueness clash is the use case's named error,
never a bare 409.
documentation/api/openapi.yaml is the one source for the error body, the validation status,
the casing, and the page shape. Stack skills (typescript-fastify,fastapi, test skills) implement what it says and do not
define their own. A code the operation
cannot return misleads screen-spec and every generated client.
The status of a named use-case error is not chosen from the generic
4xx table: it is the SRS status or the answer in ANSWERS
(Step 1 mapping). Do not stop to wait for an architecture table. A status label
shown to the client is the schema's «Подпись» column in ## Закрытые наборы значений, not an example invented here.
Use $ref to components/responses for 400, 401, 403, 404,422, 429, 500, 503 (references/base-schemas.md).
Step 7 — Quality Checklist
Before delivering the spec, verify:
-
openapi: "3.0.3"present, not3.1.0 -
info.versionper Contract version;info.x-sourcespins each source aspath@<version>— Step 2 - Every path has at least one operation
- Every operation has a unique camelCase
operationIdmatching a use case or query — Step 1 - Every operation has exactly the codes Step 6 derives: a success code,
500, one response per use-case failure with the Step 1 status - Success bodies and field names match the use case and the schema's DBML — Step 1
-
requiredand anexampleon every request and response body — Step 3 - Security per Step 4; every
callbacks:entry has a signature header - Tags defined at root level to match operation tags
-
redocly lint(recommended) reports noerror(an unresolved$refis one) and nono-unused-componentswarning (an orphaned schema), or the close says why it could not run
Step 8 — Output
Layout. The contract is split into files, so a reader opens the
operation or schema it needs instead of thousands of lines. It is the layoutredocly split produces and redocly bundle reverses:
documentation/api/
├── openapi.yaml root: openapi, info (version, x-changelog,
│ x-sources), servers, security, tags; every
│ path and component listed by $ref
├── paths/bookings_{id}.yaml one file per path: /bookings/{id}
└── components/
├── schemas/Booking.yaml one file per schema
├── responses/ProblemResponse.yaml
├── parameters/…
└── securitySchemes/…openapi.yamlkeeps its path, soopenapi.yaml@<version>citations,
the linter and the client generators keep working: they follow the$refs. A generator that cannot reads a bundle made in the build
(npx --yes @redocly/cli bundle documentation/api/openapi.yaml -o <build dir>/openapi.bundled.yaml), never a committed second copy.- A new path gets its own file named as
redocly splitnames it (/→_, braces kept); a new schema, response or parameter its own file
undercomponents/; both are listed in the root. A path file refers to a
schema by its file ($ref: ../components/schemas/Booking.yaml). - Searching the contract — an
operationId, a schema, a status — runs overdocumentation/api/, not over the root alone.
doc-typistwritesdocumentation/api/openapi.yamland every file it
references (the pathsdoc-versioningnames); the orchestrator lints the
root.- The report has exactly the packet's fields. A use case left without an
operation goes to CONCERNS; no summary table. - Do not run
doc-revieworpipeline— the orchestrator closes the stage.
Do not rewrite use-case Steps inside the spec. Do not ask to generate
test cases, mock servers, or client SDKs unless the user asks.
Reference Files
references/base-schemas.md— the page,Problem, timestamps and shared responses; before writingcomponentsreferences/security-schemes.md— OpenAPI 3.0 security schemes; when the SRS names authreferences/common-patterns.md— pagination, problem+json, webhooks, file upload, filtering; when the SRS
names one of them, not by defaultreferences/descriptions.md— when writing any description (info, operation, schema, property)