---
name: openapi-operation-conventions
source: https://app.decimal.ai/s/openapi-operation-conventions@1/SKILL.md
source_sha256: 8c3c79aecf19
---

# OpenAPI operation conventions

## Contract

Author every operation in an OpenAPI 3.x description to the disciplined form: a unique camelCase
verbNoun `operationId`, every `{path}` parameter declared with `in: path` and `required: true`,
`tags` grouping, `responses` documented for both a success and an error status code, and reusable
object schemas defined once under `components/schemas` (PascalCase) and referenced by `$ref`.
Apply when writing or reviewing the operations of a spec; not for choosing URL versioning, generic
resource naming, or generating client code from a spec.

## Rules

1. **Every operation has an `operationId`.** No operation is left without one — it is the stable
   handle generators and docs key on.

2. **`operationId` is camelCase verbNoun.** A leading verb, then the noun: `listUsers`, `getUser`,
   `createUser`, `updateUser`, `deleteUser`, `listUserOrders`. Not snake_case, not the noun first
   (`userGet`), not a sentence.

3. **`operationId`s are unique across the whole document.** Two similar operations get distinct
   ids (`listActiveUsers`, `listArchivedUsers`) — never the same id twice.

4. **Every `{path}` parameter is declared.** A templated segment like `/users/{userId}` has a
   matching entry under `parameters` with `name: userId`, `in: path`, `required: true`, and a
   `schema`. `in: path` parameters are always `required: true`.

5. **Operations are grouped by `tags`.** Each operation carries at least one `tag` (`Users`,
   `Orders`), so the rendered docs and generated clients group related operations.

6. **Responses document success AND error.** `responses` is keyed by HTTP status code and lists at
   least one success code (a `2xx`) and at least one error code (a `4xx`/`5xx`). Never only a bare
   `200`. Each operation also has a short `summary`.

7. **Reusable schemas live under `components/schemas`, referenced by `$ref`.** An object used in
   more than one place is defined once under `components/schemas` with a PascalCase key (`User`,
   `Order`, `Error`) and referenced everywhere via `$ref: '#/components/schemas/User'` — never
   re-inlined per operation.

## Worked examples

Operation identity and path parameter — the thin default, then the conforming operation:

```yaml
# BEFORE
/users/{userId}:
  get:
    summary: Get a user
    responses:
      '200': { description: OK }

# AFTER
/users/{userId}:
  get:
    operationId: getUser
    summary: Get a user
    tags: [Users]
    parameters:
      - name: userId
        in: path
        required: true
        schema: { type: string }
    responses:
      '200':
        description: The user
        content:
          application/json:
            schema: { $ref: '#/components/schemas/User' }
      '404': { description: User not found }
```

Schema reuse — inlined twice vs. one `components/schemas` entry:

```yaml
# BEFORE  (the same object re-declared inline in each operation)
responses:
  '200':
    content:
      application/json:
        schema:
          type: object
          properties: { id: { type: string }, name: { type: string } }

# AFTER
components:
  schemas:
    User:
      type: object
      properties: { id: { type: string }, name: { type: string } }
# ...and each operation references it:
schema: { $ref: '#/components/schemas/User' }
```

Unique ids for similar operations:

```yaml
# BEFORE  operationId: listUsers   (on BOTH operations)
# AFTER   operationId: listActiveUsers   /   operationId: listArchivedUsers
```

## Edge cases & exceptions

- A path with two templated segments (`/orgs/{orgId}/members/{memberId}`) declares BOTH as
  `in: path`, `required: true`.
- Query and header parameters use `in: query` / `in: header`; only `in: path` is forced to
  `required: true`.
- A `204 No Content` success still counts as the documented success response (no body needed).
- A schema used in exactly one place MAY be inlined, but a shared one (request + response, or two
  operations) belongs in `components/schemas`.
- `operationId` stays stable once published — renaming it breaks generated client method names.
- Enum or primitive wrappers reused across operations also belong under `components/schemas`.

## Do / Don't

- Do give every operation a unique `operationId`. Don't leave operations id-less or duplicate an id.
- Do write ids as camelCase verbNoun. Don't use snake_case or put the noun first.
- Do declare each `{path}` parameter `in: path`, `required: true`. Don't leave a templated
  segment undeclared.
- Do group operations with `tags`. Don't ship untagged operations.
- Do document a success and an error response. Don't stop at a lone `200`.
- Do reference shared schemas by `$ref`. Don't re-inline the same object in every operation.

## Common mistakes

- Operations with no `operationId`, or the same id reused on two operations.
- snake_case (`get_user`) or noun-first (`userGet`) operation ids.
- A `/{id}` in the path with no matching `parameters` entry, or one missing `required: true`.
- No `tags`, so the docs render one flat, ungrouped list.
- Only a `200` documented — no `4xx`/`5xx`.
- The same object schema copy-pasted inline into every operation instead of one `$ref`.
- camelCase or snake_case `components/schemas` keys instead of PascalCase.

## Quick checklist

- Every operation: a unique camelCase verbNoun `operationId` and a `summary`.
- Every `{path}` segment: declared `in: path`, `required: true`.
- Every operation: at least one `tag`, and both a success and an error response by status code.
- Shared objects: one PascalCase `components/schemas` entry, referenced by `$ref`.
