write-openapi
Skill2 starsChanged 4 days agoMITView on GitHub
Write or review openapi.yaml for public REST APIs: endpoints, parameters, schemas, responses, examples, and contract compatibility. Internal gRPC contracts use write-proto.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- GitbookIO/gitbook-skillstheir own version, first seen Aug 31, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- GitbookIO/gitbook-skills★ 17same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository40 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .agents/skills/AGENTS.mdAGENTS.md
- attribute-ai-commitsSkill
- check-accessSkill
- choose-dependencySkill
- coordinate-landingSkill
- develop-featureSkill
- document-codeSkill
- git-conventionsSkill
- implement-featureSkill
- manage-versionsSkill
- monitor-ciSkill
- open-pull-requestSkill
- plan-client-server-boundarySkill
- plan-featureSkill
- plan-ui-designSkill
- prefer-small-solutionsSkill
- prepare-releaseSkill
- register-ai-agentSkill
- resolve-pr-feedbackSkill
- triage-issuesSkill
- use-a-novel-cliSkill
- use-discordSkill
- write-bash-scriptsSkill
- write-design-systemSkill
- write-dockerfilesSkill
- write-frontendSkill
- write-frontend-testsSkill
- write-github-actionsSkill
- write-go-kitSkill
- write-go-serviceSkill
- write-goSkill
- write-go-testsSkill
- write-infraSkill
- write-js-packageSkill
- write-platformSkill
- write-project-docsSkill
- write-protoSkill
- write-sqlSkill
- write-svelteSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
25 rules. About 0 are common, 6 are distinctive, and 19 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
19
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 22
Run these in order after any change to openapi.yaml:
- Very specific: line 29
Never ship a change that fails pnpm lint:openapi.
- Very specific: line 108
Deprecation process: add deprecated: true to the field or operation, add a description note explaining the replacement, then remove in the …
- Very specific: line 121
Do not use nullable: true, a 3.0 extension removed in 3.1.
- Very specific: line 162
Rule: prefer media-type level examples for full response/request bodies.
- Very specific: line 167
Prefer const over single-value enum arrays in 3.1:
- Very specific: line 177
Use enum when there are two or more values.
- Very specific: line 181
Default is true (any extra properties allowed).
- Very specific: line 183
Use additionalProperties: true for schemas that intentionally allow algorithm-specific extra fields (like jwk, which carries EdDSA x/crv, …
- Very specific: line 185
Use additionalProperties: false for schemas where no extra fields should be present.
- Distinctive: line 186
Never omit it for top-level response/request schemas — silence is ambiguous to code generators.
- Very specific: line 189
Typed
additionalPropertiesfor dynamic-key objects: When a response is a map whose keys are dynamic but whose values all share a single … - Very specific: line 214
Use lowercase kebab-case for path segments.
- Very specific: line 219
Use camelCase. Follows the pattern <entity><Operation> for resource operations:
- Very specific: line 275
Use lowercase single-word tags to group operations in documentation: health, jwk.
- Distinctive: line 282
Every element must be documented.
- Very specific: line 298
Always use a YAML block scalar (|) for multi-line descriptions.
- Very specific: line 310
Every responses status code in the spec must correspond to a reachable code path in the handler.
- Distinctive: line 312
Every response schema field must correspond to a field the handler actually serializes.
- Distinctive: line 313
Check the JSON tags on the Go struct.
- Distinctive: line 320
The spec does not define handler behavior — it describes it: If the spec and the handler disagree, fix the spec (or the handler if it is …
- Very specific: line 366
Use specific 4xx responses only for error conditions the handler explicitly checks and returns.
- Very specific: line 381
Do not document a 4xx response unless the handler code has an explicit path that returns it.
- Very specific: line 391
Every operation must have a default response.
- Distinctive: line 423
Do not add spurious 4xx responses to silence these warnings — that would document behavior the server does not have.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
OpenAPI Specification Skill
openapi.yaml is the public contract for the REST API, consumed by documentation generators,
client code generators, and API testing tools. Every field name, type, and status code is a
durable commitment once published.
Before touching openapi.yaml, read the entire file, and read the Go handler code for
every endpoint you are about to change. The spec must match exactly what the server returns,
not what you think it should return.
After Every Edit
Run these in order after any change to openapi.yaml:
pnpm format # runs Prettier over all files
pnpm lint:openapi # validates the spec with RedoclyNever ship a change that fails pnpm lint:openapi. Warnings are not errors, but document
any known, intentional one (see Suppressed Warnings).
Then update the TypeScript types in the JS client pkg/js/rest/src/ to match the spec
change, and run pnpm lint:typecheck to confirm.
Project Layout
openapi.yaml # The single-file OpenAPI 3.1 spec (edit this)
pkg/js/rest/src/ # JS client that must stay in sync with the specEverything lives in openapi.yaml; there is no multi-file splitting. Use $ref for
reusable components defined under components/ in the same file:
$ref: "#/components/schemas/jwk"
$ref: "#/components/responses/notFound"
$ref: "#/components/parameters/jwkID"Toolchain
Linting runs Redocly CLI via pnpm redocly lint openapi.yaml. A redocly.yaml at the
project root extends the recommended ruleset with project-specific overrides; recommended
is opinionated but not maximally strict, and some of its rules warn rather than error.
Rule overrides live in redocly.yaml:
extends: [recommended]
rules:
operation-4xx-response: off # suppress for endpoints with no 4xxVersioning
The spec version (info.version) must match package.json "version" and is updated
automatically by the publish scripts. Never change info.version by hand — a manual
edit diverges the YAML from package.json.
Breaking vs Non-Breaking Changes
The REST API is public, so any change that breaks existing callers needs a major version bump and coordination across consuming services.
Breaking — never do without a major version bump
| Change | Why it breaks callers |
Remove an endpoint (/jwk, /jwks, etc.) | Callers get 404 |
| Remove or rename a required response field | Callers accessing the field get undefined/panic |
Change a field's type (e.g., string → integer) | Callers fail to deserialize |
Change a 2xx status code (e.g., 200 → 201) | Callers checking the code break |
Add required: true to a previously optional parameter | Callers omitting it now get 400 |
Rename an operationId | Code generators and clients using operationId break |
Remove a named $ref component that is used | Generated clients break |
Non-breaking — always safe
| Change | Notes |
| Add a new endpoint | Old clients never call it |
| Add an optional response field | Old clients ignore unknown fields |
| Add a new optional query parameter | Old callers don't send it; server still works |
| Add a new error status code (4xx, 5xx) | Old callers already handle unknown errors |
| Change a description or example | No runtime impact |
Add a new $ref component | Unused until referenced |
Mark a field deprecated: true | Signals intent without breaking callers |
Deprecation process: add deprecated: true to the field or operation, add a
description note explaining the replacement, then remove in the next major version. Never
remove a deprecated item in the same release it was deprecated.
OpenAPI 3.1 Specifics
This project uses OpenAPI 3.1, which aligns fully with JSON Schema Draft 2020-12. Key differences from 3.0 that affect writing this spec:
Nullable fields
Do not use nullable: true, a 3.0 extension removed in 3.1. Use type unions instead:
# WRONG (3.0 style):
type: string
nullable: true
# CORRECT (3.1):
type: [string, "null"]Examples
Three placement options exist, from most to least specific:
Media-type level (
content.{media}.examples) — named examples object, the most flexible; Redocly renders all of them. Use for multiple named examples on an operation response.content: application/json: examples: active: value: { "status": "up" } degraded: value: { "status": "down", "err": "connection refused" }Schema level (
schema.examples) — JSON Schema array; only the first item is typically rendered. Use for property-level inline examples, or when one example is enough.schema: type: string examples: [auth, auth-refresh]- Schema
example(singular, 3.0 compat) — single value, lower priority than the array form. Avoid in new code; useexamplesinstead.
Rule: prefer media-type level examples for full response/request bodies. Use
schema-level examples (array) for individual parameters and schema properties.
const and enum
Prefer const over single-value enum arrays in 3.1:
# WRONG (verbose):
enum: [sig]
# CORRECT (3.1):
const: sigUse enum when there are two or more values.
additionalProperties
Default is true (any extra properties allowed). Be explicit:
- Use
additionalProperties: truefor schemas that intentionally allow algorithm-specific extra fields (likejwk, which carries EdDSAx/crv, RSAn/e, etc.). - Use
additionalProperties: falsefor schemas where no extra fields should be present. - Never omit it for top-level response/request schemas — silence is ambiguous to code generators.
Typed additionalProperties for dynamic-key objects. When a response is a map whose keys
are dynamic but whose values all share a single shape, use a $ref (or inline schema) as the
value of additionalProperties. Generators then know the value type without every key listed:
health:
type: object
required: ["client:postgres"]
additionalProperties:
$ref: "#/components/schemas/healthStatus" # every key maps to this type
properties:
"client:postgres": # explicitly document known keys
$ref: "#/components/schemas/healthStatus"The properties block calls out the key that must always be present (matched by required);
additionalProperties covers future keys added without a spec change. On a map with typed
values, additionalProperties: true reads as any to generators and loses type safety.
Naming Conventions
Paths
Use lowercase kebab-case for path segments. This project uses flat paths (/jwk,
/jwks, /ping, /healthcheck) — follow this pattern for new endpoints.
operationId
Use camelCase. Follows the pattern <entity><Operation> for resource operations:
| Operation | operationId |
| List JWKs | jwkList |
| Get one JWK | jwkGet |
| Ping | ping |
| Healthcheck | healthcheck |
operationId values must be unique across the entire spec. Code generators use them to name
functions, so treat them like function names. Renaming one is breaking; add a new operation
instead if a rename is truly needed.
Schema names
camelCase for reusable schemas under components/schemas, matching the $ref keys the fleet's
specs already use:
| Name | Use |
jwk | A public JSON Web Key |
jwkID | The UUID identifier of a key |
healthStatus | Status of a single dependency |
An initialism keeps its casing after the first segment (jwkID, not jwkId).
Parameter names
camelCase for the $ref key under components/parameters; snake_case for the
actual name in the query string (to match what the Go handler reads with
r.URL.Query().Get("id")):
components:
parameters:
jwkID: # camelCase $ref key
name: id # snake_case query param name
in: queryResponse names
camelCase:
components:
responses:
jwkList:
jwkGet:
notFound:
badRequest:
internalError:Tags
Use lowercase single-word tags to group operations in documentation: health, jwk.
Define tags at the top level under tags: with descriptions if more than two exist.
Documentation Requirements
Every element must be documented. No exceptions.
| Element | Required |
info.description | Multi-line markdown; explain the service's purpose and scope |
| Each path operation | summary (one line) + description (markdown, explains behavior and edge cases) |
| Each parameter | description explaining what the value controls |
| Each response | description — one sentence is sufficient for error responses |
| Each schema | description |
| Each schema property | description |
Each named $ref component | Documented inside the component definition |
Summary vs description:
summary— one sentence, no markdown, shown in navigation/tooling.description— markdown allowed; carries nuance, caveats, and edge-case behavior. Always use a YAML block scalar (|) for multi-line descriptions.
Examples — every schema property with a known range of values must include examples.
Parameters that accept a finite set of identifiers (like usage) must show real values from
the server config, not placeholders.
Alignment with the Go Handler Layer
The spec must exactly mirror what the Go handlers return. Rules:
- Every
responsesstatus code in the spec must correspond to a reachable code path in the handler. If the handler can never return 422, do not document 422. - Every response schema field must correspond to a field the handler actually serializes. Check the JSON tags on the Go struct.
- Parameter
required: truemust match whether the handler actually validates and rejects missing inputs with 400. - The
usageparameter values documented as examples must match the registered usages ininternal/config/jwks.config.yaml(auth,auth-refresh). Those are named signing configuration identifiers, not JWKusevalues (sig,enc).
The spec does not define handler behavior — it describes it. If the spec and the handler disagree, fix the spec (or the handler if it is wrong), but never let them drift.
Response Design
Status codes
Pick each code by its RFC 9110 meaning and send the headers that meaning requires. Handlers, platform routes and specs all follow this table:
| Outcome | Code | Required headers |
| A read, or an action that returns a body | 200 | |
| Created an addressable resource | 201 | Location naming it |
| Accepted work that finishes later | 202 | Location of a status resource |
| Succeeded with nothing to return | 204 | no body |
Conditional GET on an unchanged resource | 304 | the ETag and Cache-Control of 200 |
Redirect after a POST (POST/redirect/GET) | 303 | Location |
| Moved, temporarily or for good, method kept | 307 / 308 | Location |
| Missing or invalid credentials | 401 | WWW-Authenticate |
| Authenticated but not allowed | 403 | |
| Method not served on this path | 405 | Allow |
| Write conflicts with the current state | 409 | |
If-Match or If-Unmodified-Since failed | 412 | |
| Rate limited | 429 | Retry-After |
- Never
301or302. A client may turn a redirectedPOSTinto aGETon either, so the method depends on the client.303,307and308state it. A redirect answering a request that carried credentials uses303, never307, which reposts them (RFC 9700 §4.12). - Another user's resource is
404. Answering403confirms the resource exists. - Cache what is public and stable. A public read that changes rarely, such as a JWKS, sends
Cache-Controland anETag, and answersIf-None-Matchwith304.http.ServeContentdoes the conditional part once theETagheader is set. 1xxcomes from the stack. Go's server sends100 Continueitself when a handler first reads a body sent withExpect: 100-continue; never write it by hand.103 Early Hintssuits an HTML page whose critical assets are known before its body; confirm the load balancer forwards it first.- A changed
2xxis breaking. Fixing a published route's success code waits for a planned major (see Breaking vs Non-Breaking Changes). Adding a required header such asWWW-AuthenticateorAllowis not.
Error responses
Use specific 4xx responses only for error conditions the handler explicitly checks and
returns; everything else falls to the default response.
responses:
"200":
$ref: "#/components/responses/jwkGet"
"400":
$ref: "#/components/responses/badRequest" # only if the handler validates input
"404":
$ref: "#/components/responses/notFound" # only if the handler returns 404
default:
$ref: "#/components/responses/internalError"Do not document a 4xx response unless the handler code has an explicit path that returns
it. A 400 from /jwk (invalid UUID) is a real handler path; a 400 from /jwks does not
exist (see Suppressed Warnings).
A rate limit is such a path. Every limited route references one tooManyRequests component,
429 with an integer Retry-After header; only the unlimited liveness ping omits it. A route with
a strict limit states its numbers in its description, so clients can pace themselves.
The default response
Every operation must have a default response. It catches any status code not explicitly
listed: internal errors and unexpected conditions.
Reuse response components
Define all responses under components/responses and reference them; never inline a
response schema:
responses:
"404":
$ref: "#/components/responses/notFound" # NOT inlineSuppressed Warnings
Three operations intentionally have no 4xx responses. Without operation-4xx-response: off
in redocly.yaml, Redocly would flag them with:
openapi.yaml: Operation must have at least one `4XX` response. [operation-4xx-response]
Affected: /ping GET, /healthcheck GET, /jwks GETNone of these handlers returns a 4xx, by design:
/ping— always returns 200 (liveness check only)./healthcheck— always returns 200 regardless of dependency status (callers read the body to assess health)./jwks— accepts any string forusageand returns an empty list for an unrecognized usage; it never validates input with a 400.
Do not add spurious 4xx responses to silence these warnings — that would document behavior the server does not have.
The suppression in the root redocly.yaml:
extends: [recommended]
rules:
operation-4xx-response: offCommon Pitfalls
Each of these is covered above; run the list as a checklist before shipping an edit.
nullable: trueinstead of a 3.1 type unionusageexamples taken from JWKusevalues instead ofinternal/config/jwks.config.yaml- A 4xx documented on a path the handler never returns
- An inline response schema instead of a
components/responses$ref - A renamed
operationId - 3.0 singular
exampleon a new schema pkg/js/rest/src/types left stale after a response-schema change- A new required parameter on an existing endpoint
- A hand-edited
info.version - Single-item
enumwhereconstfits additionalPropertiesomitted on a closed schema
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

