How-to

How to fix duplicate operationId errors in OpenAPI

A duplicate operationId is invalid per the OpenAPI spec text, but plain JSON-Schema-level tooling won't catch it — only a semantic linter will, and if nothing catches it, your SDK generator silently overwrites one method with another, or your MCP server silently drops a tool. The fix is to rename one of the two operations; the harder part is knowing you have the problem before a generator hides it from you. Reproduced with real tools on 2026-09-16.

What does the spec actually require?

Every operationId must be unique across the entire document, not just within one path. Per the OpenAPI 3.1 specification text, Operation Object section: "Unique string used to identify the operation. The id MUST be unique among all operations described in the API." That's document-wide — two different paths, two different HTTP methods, anywhere in the file, must not share an operationId. This rule is unchanged from OpenAPI 3.0 through 3.1.

How do you reproduce the error?

We built a minimal spec with the same operationId on two different operations (GET /messages and GET /messages/{id}, both operationId: listMessages) and ran two real validators against it on 2026-09-16.

swagger-cli validate v4.0.4 (AJV against the official JSON Schema): reports the file as valid. The JSON Schema for OpenAPI doesn't express "unique across the document" as a schema constraint — it's a semantic rule, not a structural one, so a pure schema validator cannot see it.

redocly lint v2.53.2 (default ruleset): catches it immediately —

[2] api-dup.yaml:21:7 at #/paths/~1messages~1{id}/get/listMessages
Every operation must have a unique `operationId`.
Error was generated by the operation-operationId-unique rule.

That gap is the practical lesson: if your CI only runs a JSON-Schema-style validator (swagger-cli, or any bare AJV check against the OpenAPI meta-schema), a duplicate operationId will pass. You need a semantic linter — Redocly CLI, Spectral, or the free in-browser OpenAPI validator — in the loop specifically to catch this one.

Why does it matter more than a lint warning?

Because operationId is what SDK generators turn into a method name and what MCP generators turn into a tool name — and both downstream systems have their own uniqueness rules that a silently-broken spec will violate in different, harder-to-debug ways:

  • SDK generation. operationId becomes the method name (camelCase in TypeScript, snake_case in Python). Two operations sharing one operationId means the generator either errors, or — depending on the tool — silently produces one method that only reflects the last operation processed, dropping the other endpoint from the generated client with no error.
  • MCP tool generation. Per the Model Context Protocol tools specification, each tool is uniquely identified by its name field, scoped to a single server. When operationId maps directly to tool name (the common pattern — see generating an MCP server from OpenAPI), a duplicate operationId produces two tool definitions with the same name on the same server. MCP hosts are not required to handle that gracefully; expect either a rejected registration or one tool silently shadowing the other.

Neither failure mode necessarily throws a build error. That's the actual risk: a duplicate operationId can ship in a generated SDK or MCP server and only surface when a customer calls the endpoint that got silently dropped.

How do you fix duplicate operationIds?

Rename one of the two operations to something that describes what makes it different — don't just append a number:

# Before — both operations share one operationId
/messages:
  get:
    operationId: listMessages
/messages/{id}:
  get:
    operationId: listMessages   # duplicate

# After
/messages:
  get:
    operationId: listMessages
/messages/{id}:
  get:
    operationId: getMessage

getMessage (or getMessageById) is both spec-valid and produces a sane method/tool name; listMessages2 is spec-valid but produces a worse SDK and a worse tool description for an agent trying to pick the right tool.

How to catch this before it ships

  1. Add a semantic linter to CI, not just a schema check — redocly lint or spectral lint both include an operationId-uniqueness rule by default; a bare AJV/JSON-Schema check will not, as reproduced above.
  2. If you're generating both an SDK and an MCP server from the same spec, validate once and generate from the validated file — don't let two separate pipelines each discover the problem independently (or not at all).
  3. Run the spec through the free in-browser OpenAPI validator before wiring up generation; it flags missing and duplicate operationIds as part of the standard check.

If you're generating an MCP server or SDKs from a spec you didn't write, Sourced's scanner checks operationId hygiene (missing values and casing consistency) as part of every spec push, before generated tool names or method names reach a customer or an agent — start a free report.