# operationId and tags in OpenAPI: naming conventions that keep generated SDKs and docs usable

> Source: <https://dev.to/jeff_pdc/operationid-and-tags-in-openapi-naming-conventions-that-keep-generated-sdks-and-docs-usable-2hpp>
> Published: 2026-10-08 04:42:39+00:00

Open a generated SDK where the methods are named `getV1UsersByIdGet`, `postV1UsersPost`, and `usersGet2`, and the cost of careless `operationId` s is immediate: nobody can discover anything, and every rename is a breaking change for downstream code. Open the docs sidebar and see 200 operations in one flat list because every endpoint got its own tag, and the same problem appears on the human side. These two fields look like optional labels; they are the public naming API of your service.

| Field | Consumed by | Consequence of getting it wrong | 
|---|---|---|
| `operationId` | Code generators, mocking tools, agent tool lists | Becomes the method/function name; must be unique and stable | 
| `tags` | Documentation navigation, generator namespaces | Groups operations into sections or SDK classes | 
| `summary` /`description` | Docs, AI tool descriptions | The sentence a developer or agent reads first | 
| `x-*` group names | Some renderers | Vendor-specific grouping; do not rely on it portably | 

Generators that do not find an `operationId` synthesize one from the method and path, which is how you get `getV1UsersByIdGet`. Once a synthesized name ships, fixing it later breaks every caller. Set the id deliberately from day one.

**Unique across the whole document.** Two operations cannot share an id; some generators dedupe by appending a number, silently. Enforce uniqueness with a linter.

**Language-neutral and code-safe.** Use `lowerCamelCase` with ASCII letters and digits, starting with a letter. Avoid hyphens, dots, and spaces, because not every language maps them cleanly into an identifier.

**Verb-first, resource-oriented, specific.** The id should read as an action on a resource and say enough to be unambiguous without the path:

| Method and path | Avoid | Prefer | 
|---|---|---|
| GET /users | `getUsersGet` | `listUsers` | 
| POST /users | `postUsers` | `createUser` | 
| GET /users/{id} | `getUserById` (fine) or`getV1UsersId` | `getUser` | 
| PATCH /users/{id} | `updateUser` (ambiguous vs PUT) | `patchUser` /`updateUser` split by verb | 
| DELETE /users/{id} | `deleteUsersId` | `deleteUser` | 
| POST /users/{id}/archive | `archive` | `archiveUser` | 
| GET /users/{id}/orders | `getOrders` | `listUserOrders` | 

A consistent verb vocabulary removes the guesswork: `list` for collections, `get` for one, `create`, `update`/` patch`, `delete`, plus domain actions like `archive`, `cancel`, `approve`, `export`. Avoid generic verbs like `process` or `handle` that say nothing.

**Stable forever once published.** The id is part of the contract because generated code embeds it. Renaming it is a breaking change for SDK users even if the URL does not change. Treat it like a field name: pick it once, lint it, and never reuse a retired id for a different operation.

**Do not encode the version or HTTP method.** `getV2Users` bakes the version into the method, so a future v3 forces a rename even for clients that never cared. Version belongs in the server URL or path, not the id.

Tags group operations in docs and often become SDK classes or namespaces. The failure modes are a tag per endpoint (no grouping at all) and a single tag for everything (one giant list). Aim for a small, stable set aligned to business domains, not to URLs:

```
tags:
  - name: Users
    description: Customer accounts, profiles, and authentication identities.
  - name: Orders
    description: Order lifecycle, line items, and status transitions.
  - name: Billing
    description: Invoices, payment methods, and refunds.
  - name: Webhooks
    description: Event subscriptions and delivery logs.
```

Rules that keep the taxonomy usable:

`description`; do not rely on ad-hoc names appearing only on operations (typos then create duplicate tags like `User` and `Users`).` Webhooks`, but three or more tags per operation scatter it across the docs.`Billing`, `Orders`), not internal service names (` payment-svc`) or HTTP concepts.
If you need finer grouping than a tag gives, use `x-tagGroups` (supported by several renderers) to cluster tags into sections like "Catalog" and "Account" without multiplying tags.

`summary` is a short imperative label; `description` is the detail. For an AI agent that turns operations into callable tools, the summary and the first line of the description are often the entire basis for choosing the operation. Write them to disambiguate:

```
operationId: listUserOrders
summary: List a user's orders
description: >-
  Returns orders belonging to the given user, newest first. Supports cursor
  pagination and filtering by status. Does not include line items; use
  getOrder for those.
```

That one sentence ("does not include line items; use getOrder") prevents a class of wrong agent calls that a name alone cannot.

Naming conventions are exactly the kind of rule a linter should guarantee rather than a wiki page hoping people remember. A few high-value rules:

`operationId` matching an allowed verb prefix;
Run the ruleset in CI so a PR that introduces `postV2ThingsPost` fails before merge, and add a check that flags a brand-new tag, forcing a conscious decision to grow the taxonomy.

`client.users.list()` style APIs; a missing id yields path-derived noise.`listUserOrders` over `listOrders` correctly. Ambiguous or missing ids make the agent guess, and the guess lands in production calls.
Get these right and the generated SDK reads like an API a human designed, the docs navigate cleanly at 200 routes, and an AI agent picks the right operation without guessing.

You can apply these conventions, generate a cleanly named TypeScript client, and lint the result all in one local-first workspace, [right in your browser](https://www.powerduck.com/app/). To see how the ids and tags surface in a generated SDK, read [generating a TypeScript client from OpenAPI](https://www.powerduck.com/blog/generate-typescript-client-from-openapi/).
