← All writing

ADL vs OpenAPI

The Contract and the Counterparty

TL;DR: OpenAPI describes an HTTP surface: paths, payload shapes, auth schemes. It says nothing about what happens when a request arrives: who may make it, in which states, under which business rules. It’s a contract awaiting an implementation, and the implementation can drift from it the day after it’s written. ADL specifies the behavior itself, and the Kalos runtime enforces it directly. The relationship between the two isn’t rivalry — it’s derivation, and it only goes one way. Every Kalos domain emits a complete OpenAPI 3.1 document as a byproduct, guaranteed accurate because it’s generated from the same model the runtime enforces. You can get OpenAPI from an application. You can never get an application from OpenAPI.


Give OpenAPI its due

OpenAPI won, and it deserved to. It’s the closest thing the API world has to a universal contract format: vendor-neutral, governed by a foundation, and surrounded by the largest toolchain in the specification business: client generators for every language, mock servers, contract-testing frameworks, Swagger UI, Redoc, gateway integrations. If you publish an API in 2026 and don’t publish an OpenAPI document, you’ve made your consumers’ lives harder for no reason.

Nothing in this article argues against using OpenAPI. Kalos generates it for every domain, automatically, at /api/v1/domains/{name}/openapi.json, precisely because the ecosystem around it is too valuable to ignore. The argument is about what OpenAPI is, and what people quietly expect it to be.

What an OpenAPI document actually contains

Strip an OpenAPI document to its semantic content and you get: a list of paths and methods; the shapes of request and response bodies; parameter definitions; named security schemes; and human-readable descriptions. That’s the surface of an API — the doorframe dimensions, not the building.

Now list what a real application must decide, none of which OpenAPI can express:

Which roles may call each operation: not “this endpoint uses bearer auth” (a scheme), but “update requires owner, editor, or admin, and owner means the user whose ID matches this record’s authorId.” Whether a write is legal given the record’s current state: that a published article can’t be edited by its author, that a cancelled order accepts no transitions at all. Whether a write is legal given the rest of the data: that this enrollment would exceed the study’s cap, that this effort commitment would push a person past 100%. What happens as a consequence of a write: the timestamp that gets set, the history event that gets recorded, the notification that fires, the aggregate that recalculates on the parent record.

Every one of those decisions exists in every serious backend. In an OpenAPI-first shop, every one of them lives in hand-written code that the specification cannot see, cannot validate, and cannot keep honest.

The dotted line in “design-first”

The design-first movement (write the OpenAPI document before the code) is a genuine improvement over documentation-as-afterthought. But look at what it actually guarantees. Contract tests can verify that your endpoints exist and your payloads match the declared shapes. They cannot verify that your permission checks are right, because the contract never stated them. The spec and the implementation agree about the doorframe and are free to disagree about everything behind the door.

This is the description-versus-execution gap from the first article in this series, in its most familiar costume. OpenAPI is a description language, and a good one. The behavior still lives somewhere else — and two artifacts, maintained under different pressures, drift.

There’s a revealing asymmetry in the tooling that grew up around OpenAPI. Server-side generators exist. They produce stubs: route registrations and empty handler bodies with // TODO: implement inside. That TODO is the entire point. OpenAPI underdetermines the application so radically that the most a generator can do is sketch the doorframes and hand you the building permit. The interesting 90% (the semantics) was never in the document to begin with.

The ADL relationship: emit, don’t compete

An ADL domain declares the things OpenAPI can’t. Permissions are part of the resource definition, down to field level: update: [owner, editor, admin] isn’t documentation, it’s the check the middleware runs on every request. State machines declare which transitions exist, who may execute them, and what fields they require. Validation rules and cross-record business rules run before every write commits. All of it in one YAML document that the Kalos runtime interprets directly.

Because the runtime holds the complete model, deriving the surface description is mechanical. Kalos walks the model and emits OpenAPI 3.1: three schemas per resource (read shape, input shape with computed and auto-generated fields correctly excluded, list envelope), every CRUD route, every transition endpoint, every relationship route, the standard query parameters, the error envelope. Where OpenAPI’s vocabulary runs out, extension fields carry what they can (x-from-states and x-required-fields on transition operations, x-enum-labels on enums), honest annotations at the edge of what the format can say.

This emitted document has a property no hand-maintained OpenAPI file has ever had: it cannot be wrong. It isn’t a parallel artifact someone promises to update. It’s a projection of the same model the runtime enforces, regenerated from source on demand. Contract and implementation can’t drift when the contract is computed from the implementation’s single source of truth.

And the derivation only runs in that direction. Feed the emitted document to openapi-typescript and you get fully typed clients for a Kalos backend: the polyglot story, solved by the standard rather than by porting the engine. But feed an OpenAPI document to anything and you cannot get the application back, because the application was never in it.

Where OpenAPI is simply better

Two honest concessions.

First, neutrality. OpenAPI is a standard; ADL is one vendor’s language with one runtime. If your deliverable is the contract (a public API consumed by strangers, a surface implemented independently by multiple teams), the neutral format is the right artifact to center, and ADL’s role is to be one very disciplined way of producing and honoring it.

Second, reach. OpenAPI describes any HTTP API, including the ones ADL could never express: the genuinely novel, algorithm-heavy services where a declarative model is the wrong tool. OpenAPI’s weakness and its universality are the same property: it says so little about behavior that it can describe anything. ADL says so much about behavior that it can only describe what fits its model. That trade is the entire subject of this series.

The one-line version

OpenAPI tells your consumers what the API looks like. ADL tells the runtime what the application is — and the runtime hands your consumers the OpenAPI for free.

Next in the series: ADL vs SQL, the one specification language that already runs, and why it stops at the table.