OpenAPI spec and the AI coding loop
OriginChainDB publishes a vanilla OpenAPI 3.1 spec at /openapi.json because that is the file a coding agent reads when an engineer asks it to build a client. Nobody filed an issue asking for one. The spec is the floor the whole SDK-generation loop stands on, and a vendor without one is not reachable from inside the agents engineers actually use.
The SDK-generation loop
Here is the workflow, and it is roughly universal now:
- An engineer wants to call OriginChainDB from a Go service.
- They open their coding agent and type: “build me a Go client for OriginChain. Here’s the OpenAPI spec: https://acme.originchain.ai/openapi.json.”
- The agent fetches the spec, reads the path patterns, response shapes, auth scheme, and error envelope, and either:
- runs
openapi-generator-cli generate -i... -g go -o./oc-client, then patches the bits the generator gets wrong, or - generates the client from scratch with full control over package layout, naming, and idiom.
- Thirty seconds later, the engineer has a working
oc.NewClient(token).SQL(ctx,...)they can drop into their service.
This loop runs hundreds of times a day across every database vendor that has a spec. The vendor never sees it - there is no telemetry, no sign-up, no email - but it is happening, and it is the dominant integration path.
If you do not ship a spec, you are invisible to that loop. The agent has no source of truth to read from; the engineer falls back to guessing path shapes from your docs page; the result is a half-broken client that gets thrown away. Worse: if your competitor’s spec is there, the agent silently picks the path of least resistance, and you lose the integration before the engineer ever read your homepage.
This is the shape of distribution in 2026. Specs are the new SDKs.
What our spec contains
https://<tenant>.originchain.ai/openapi.json and the canonical mirror at https://originchaindb.com/openapi.json are vanilla OpenAPI 3.1 - no extensions, no vendor-specific quirks, no auth flow that needs explanation. It is the simplest possible shape for a generator to consume.
Auth. One scheme: bearerAuth, HTTP Authorization: Bearer <token>. Every protected path declares it. There is no OAuth flow, no API-key header alternative, no session cookie. One auth path means generators produce one auth method, which means the agent doesn’t have to decide between three.
Paths. Six of them: one per query shape, plus schema introspection.
POST /v1/tenants/{tenant}/sql Typed SQL with bind params
POST /v1/tenants/{tenant}/ask Natural-language question → rows
POST /v1/tenants/{tenant}/vector/search HNSW top-k
POST /v1/tenants/{tenant}/fts/search BM25 full-text
POST /v1/tenants/{tenant}/graph/dijkstra Weighted shortest path
GET /v1/tenants/{tenant}/schemas Tenant table introspection
Response shapes. Every successful response is a { rows: [...], meta: {... } } envelope. Every error is a { error: { code, message, details } } envelope. The envelope is a generated schema, not a freehand example, so the generator emits a Response<T> wrapper type the SDK user can pattern-match on.
Examples. Each path has at least one examples block on the request body and one on the 200 response. Generators that consume examples (most of the modern ones do) lift them straight into the SDK as fixture tests.
A 10-line example
Feed the spec to a coding agent in a fresh session:
Here is an OpenAPI spec: https://originchaindb.com/openapi.json
Generate a minimal TypeScript client with one method per path,
typed request and response, bearer-token auth, and a single
`OriginChain` class. No external deps beyond fetch.
What comes back is roughly:
export class OriginChain {
constructor(private base: string, private token: string) {}
private async post<T>(path: string, body: unknown): Promise<T> {
const r = await fetch(`${this.base}${path}`, {
method: "POST",
headers: {
"Authorization": `Bearer ${this.token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!r.ok) throw new Error(`${r.status}: ${await r.text}`);
return r.json as Promise<T>;
}
sql<T = unknown>(req: { sql: string; params?: unknown[] }) {
return this.post<{ rows: T[]; meta: object }>("/v1/tenants/{tenant}/sql", req);
}
ask<T = unknown>(req: { question: string }) {
return this.post<{ rows: T[]; meta: object }>("/v1/tenants/{tenant}/ask", req);
}
vectorSearch<T = unknown>(req: {
table: string; vector: number[]; k: number;
metric?: "cosine" | "dot" | "l2";
mode?: "fast" | "high_recall";
filter?: Record<string, unknown>;
}) {
return this.post<{ rows: T[]; meta: object }>("/v1/tenants/{tenant}/vector/search", req);
}
}
Thirty seconds, no human in the loop, working client. The whole point of shipping the spec is making this trajectory the default.
What a static spec misses
Three things a vanilla OpenAPI spec cannot do, called out so the claim sheet is honest:
- Runtime introspection. The spec describes
GET /v1/tenants/{tenant}/schemasbut does not contain your tenant’s actual tables. The agent generating a client knows about the shape of the introspection call but not about yourproductstable specifically. For that the agent calls/v1/tenants/{tenant}/schemasat runtime - which is the right answer (data shouldn’t live in the spec), but worth saying out loud. - Examples that match real tenant data. The
examplesblocks in the spec are generic -"sql": "SELECT * FROM products LIMIT 10". They are not your products. A generator that wants to produce fixture-realistic tests has to pull a sample from the tenant after auth, and most generators don’t go that far. - Type precision around the JSON-Plan trees. OriginChainDB returns the executed plan tree in the
meta.planfield for SQL responses, and the tree is recursive:ScanandFilterandProjectandHashJoinare all variants of aPlanNodediscriminated union. OpenAPI 3.1 supportsoneOfdiscriminators, which we use, but most generators flatten the union tounknownrather than emit a tagged sum type. The user’s SDK ends up correct on the wire but loose at the type level. We accept this - the alternative is shipping a custom code generator, which is the kind of vendor lock-in we work hard to avoid.
None of these are reasons not to ship the spec. They are the reasons to ship the spec and the language SDKs we hand-write for the precision cases (TypeScript, Python, Go) - the spec is the floor, the SDK is the ceiling, and the agent loop fills in between them.
What this is NOT
The spec at /openapi.json is the public surface only. It does not document:
- The internal management API for tenant provisioning (which is operator-only).
- The replication protocol between primary and follower (which is private wire format).
- The
/admin/*endpoints (which are gated by a separate bearer scope).
We deliberately keep the spec narrow. Every endpoint in the spec is one we commit to maintaining the shape of across a major version. If it is in /openapi.json, it is part of the contract; if it is not, it is implementation detail and may move.
Try it
The spec lives at /openapi.json. The hand-written SDKs and full reference docs live at /docs/api. Both are stable surfaces; both are versioned in lockstep with the engine.