DEV Community

Jeff
Jeff

Posted on Originally published at powerduck.com

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

Open a generated SDK where the methods are named getV1UsersByIdGet, postV1UsersPost, and usersGet2, and the cost of careless operationIds 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.

What each field actually drives

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.

operationId rules

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: a small, curated taxonomy

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.
Enter fullscreen mode Exit fullscreen mode

Rules that keep the taxonomy usable:

  • Declare tags once at the root with a description; do not rely on ad-hoc names appearing only on operations (typos then create duplicate tags like User and Users).
  • Give each operation one primary tag for navigation. A second tag is occasionally useful for cross-cutting groups like Webhooks, but three or more tags per operation scatter it across the docs.
  • Name tags as domains a customer recognizes (Billing, Orders), not internal service names (payment-svc) or HTTP concepts.
  • Keep the set stable; renaming a tag rearranges every generated SDK namespace and the docs sidebar.

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 and description help both humans and agents

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.
Enter fullscreen mode Exit fullscreen mode

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

Enforce it with a linter

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:

  • every operation has a unique, camelCase operationId matching an allowed verb prefix;
  • every operation uses at least one tag that is declared at the root;
  • tags and operationIds do not contain version numbers or HTTP method names;
  • every operation has a summary;
  • no two operations resolve to the same generated method name.

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.

What codegen and AI callers do

  • openapi-generator, openapi-typescript-style fetchers, and most SDK generators turn operationId directly into the method or function name, often grouping by the first tag. A clean id yields client.users.list() style APIs; a missing id yields path-derived noise.
  • Mocking and contract tools key recorded examples by operation, so stable ids make mock fixtures and traces readable.
  • An AI agent enumerates operations as tools; a precise id plus a disambiguating summary is what lets it pick listUserOrders over listOrders correctly. Ambiguous or missing ids make the agent guess, and the guess lands in production calls.

Checklist

  1. Set a unique, ASCII, lowerCamelCase, verb-first operationId on every operation from the first commit.
  2. Use a fixed verb vocabulary (list, get, create, update/patch, delete, plus domain actions).
  3. Keep version and HTTP method out of the id; never rename or reuse a published id.
  4. Declare a small, curated set of domain tags at the root with descriptions.
  5. Give each operation one primary tag; use tag groups rather than many tags for sections.
  6. Write a summary and a first description line that disambiguates similar operations for agents.
  7. Lint uniqueness, casing, allowed verbs, and declared tags in CI.

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. To see how the ids and tags surface in a generated SDK, read generating a TypeScript client from OpenAPI.

Top comments (0)