> For the complete documentation index, see [llms.txt](https://docs.opendatadiscovery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opendatadiscovery.org/developer-guides/architecture-decision-log/adr-0008-openapi-tag-per-resource-scoping.md).

# ADR-0008: OpenAPI tags scope the generated API interfaces

## Status

**Accepted.** Reconstructed from the codebase on 2026-05-30; the decision is live in the source today.

## Context

ADR-0001 establishes that controllers implement OpenAPI-generated `*Api` interfaces rather than carrying their own HTTP mappings. That raises a follow-on question the spec must answer: **what determines how those generated interfaces are carved up?** With the `spring` generator, the answer is the OpenAPI **tag** — but only if the spec uses tags consistently. If operations carried several tags, or none, the generated interface boundaries would be ambiguous or arbitrary.

ODD's `openapi.yaml` adopts a deliberate tagging convention so the generated interfaces are predictable.

## Decision

**Every operation carries exactly one tag, and the generator is configured to emit one interface per tag (`useTags: true`).** A tag is therefore the unit that shapes the generated API surface: tag `alert` produces `AlertApi`, tag `search` produces `SearchApi`, tag `dataEntity` produces `DataEntityApi`, and so on. Across all 194 operations in the spec there are no multi-tagged and no untagged operations, so every operation lands in exactly one generated interface — there is no ambiguity about which interface an endpoint belongs to.

**Tags are resource-oriented, and for most resources the tag also lines up with a single URL prefix.** Of the 33 tags, 30 group operations that all sit under one `/api/<resource>` path prefix (for example every `alert` operation is under `/api/alerts`, every `search` operation under `/api/search`). For those resources the tag, the URL prefix, and the generated interface coincide cleanly.

**Three tags deliberately group a resource whose operations span more than one path root**, so the tag is broader than a single URL prefix:

* `dataEntity` (the largest, \~37 operations) spans `/api/dataentities/**` and `/api/dataentitygroups/**` — the entity and its group sub-resource.
* `ownerAssociationRequest` spans `/api/owner_association_request/**` and the owner-mapping operations under `/api/owners/**`.
* `dataCollaboration` spans `/api/datacollaboration/**` and the message-resolution operation under `/api/messages/**`.

For these, the tag is the resource boundary and the generated interface aggregates operations across the related paths. The invariant the spec actually holds is **one tag per operation → one interface per tag**; the "one tag = one URL prefix" alignment is the common case (30 of 33), not a universal rule.

## Consequences

* A contributor editing `openapi.yaml` controls which generated interface an endpoint belongs to by setting its tag; the controller then implements that interface (per ADR-0001). Tag choice is an interface-design decision, not cosmetic.
* The generated `*Api` interfaces are resource-shaped and stable, which keeps controllers small and their grouping predictable.
* For the three multi-root tags, an endpoint's URL prefix does not by itself tell you its interface — the tag does. A reader mapping a path to its generated interface must read the operation's tag, not infer it from the URL.
* Because grouping is driven entirely by the tag, a missing or wrong tag on a new operation would misplace it in the generated surface; the single-tag-per-operation discipline is what keeps the mapping unambiguous.

## Evidence

* `odd-platform-api-contract/build.gradle:10,24` — `generatorName = "spring"` with `configOptions` including `useTags: "true"`, which makes the generator emit one interface per tag; `:16` sets `apiPackage = "org.opendatadiscovery.oddplatform.api.contract.api"` and `:44` wires `compileJava.dependsOn tasks.openApiGenerate`.
* `odd-platform-specification/openapi.yaml` — across all 194 operations, each declares exactly one `tags:` entry (no operation is multi-tagged or untagged); 33 distinct tags are used.
* Clean single-prefix examples: every `alert` operation is under `/api/alerts` (e.g. `getAllAlerts` on `GET /api/alerts`); every `search` operation under `/api/search` (e.g. `search` on `POST /api/search`).
* Multi-root tags: `dataEntity` operations appear under both `/api/dataentities/**` and `/api/dataentitygroups/**`; `ownerAssociationRequest` under `/api/owner_association_request/**` and `/api/owners/**`; `dataCollaboration` under `/api/datacollaboration/**` and `/api/messages/**`.

## See also

* [ADR-0001 — Contract-first HTTP layer](/developer-guides/architecture-decision-log/adr-0001-openapi-generated-controller-interfaces.md) — controllers implement these generated `*Api` interfaces; this record explains how the spec decides what those interfaces are.
