> 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-0001-openapi-generated-controller-interfaces.md).

# ADR-0001: Contract-first HTTP layer

## Status

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

## Context

ODD Platform exposes a large REST surface (hundreds of endpoints across data entities, ingestion, search, lineage, RBAC, collaboration, and more). A platform that size needs one answer to a structural question: **where does the HTTP contract live, and how is it kept consistent** between the server that implements it and the clients (the web UI, collectors, push adapters) that call it?

There are two broad options: hand-write Spring MVC/WebFlux mappings on each controller, or define the contract once in a specification and generate the wiring from it. Hand-written mappings drift — the spec and the code diverge silently, and each client re-derives the contract by hand. ODD chose the contract-first path.

## Decision

**Every REST controller in `odd-platform-api` is a thin delegate that `implements` an OpenAPI-generated `*Api` interface.** The HTTP method, path, and `produces`/`consumes` annotations live on the generated interface — compiled from `openapi.yaml` into the `org.opendatadiscovery.oddplatform.api.contract.api.*` package by the `odd-platform-api-contract` module's `openApiGenerate` build step — **never on the controller class itself**.

A controller class carries only `@RestController` and `@RequiredArgsConstructor`; each method carries only `@Override` and delegates straight to a service. Consequently, **adding or changing an endpoint is a specification-edit-and-regenerate flow**, not a controller edit.

## Consequences

* `openapi.yaml` (in `odd-platform-specification`) is the **single source of truth** for the HTTP surface. The UI's generated TypeScript client and the server's generated interfaces stay in sync by construction.
* **To add an endpoint:** edit `openapi.yaml`, regenerate, then implement the `@Override`. Putting a `@PostMapping` / `@GetMapping` directly on a controller class does **not** follow the convention — it would create a route that lives outside the contract and silently drift the spec from the code.
* The generated `*Api` interfaces are **build artifacts** and are not committed to the source tree; to read the live contract, read `openapi.yaml`.
* **Two deliberate exceptions, both external-webhook receivers.** `AlertManagerController` is fully hand-coded — it bridges an external Alertmanager webhook payload that is not part of ODD's generated contract. `EventApiController` is also fully hand-coded — it receives the Slack events webhook via a direct `@PostMapping("/api/slack/events")`, again with no generated interface. Both are a single `@RestController` with a direct `@PostMapping` because each bridges an external system's webhook payload that is not part of ODD's contract — the deliberate exceptions that prove the rule.

## Evidence

* `odd-platform-api/.../controller/AlertController.java:11-17` — `@RestController @RequiredArgsConstructor public class AlertController implements AlertApi`; the class declares no `@RequestMapping`, and its methods are `@Override` with no mapping annotations.
* The interface is imported from `org.opendatadiscovery.oddplatform.api.contract.api.AlertApi` — generated by the `odd-platform-api-contract` module's `openApiGenerate` task at build time and absent from the source tree.
* `odd-platform-specification/openapi.yaml` — the contract the `*Api` interfaces are generated from.
* The same shape holds across the module's controllers (data-entity, ingestion, search, and RBAC controllers all `implements` their generated `*Api` with no controller-level mapping annotations). The two deliberate exceptions are the external-webhook receivers, both fully hand-coded with no generated `*Api`: `AlertManagerController.java:15-21` (`@RestController`, no `implements`, `@PostMapping("ingestion/alert/alertmanager")` with a `// TODO: define OpenAPI spec` note) and `EventApiController.java:14-22` (`@RestController`, no `implements`, `@PostMapping("/api/slack/events")` — the Slack events webhook).

## See also

* [How to contribute](/developer-guides/how-to-contribute.md) — the contribution workflow, including the spec-and-regenerate loop.
* [Build and run ODD Platform](/developer-guides/build-and-run/build-and-run-odd-platform.md) — building the module that runs the OpenAPI generation.
