The API Contract
The Smarter API’s contract is published as two kinds of machine-readable files, so that clients in any language can be generated from it rather than written by hand:
a JSON Schema and an example manifest for every SAM kind, which describe the manifests that the API accepts and returns, and
an OpenAPI 3.1 document of the apis that the Smarter CLI and the Smarter Python SDK call.
Both are generated from the platform’s Pydantic models, so they can’t drift from what the platform actually does. This page is the technical reference, written for SDK authors and for backend developers. For an introduction, see Smarter OpenAPI Contract, and for the design goals that the contract and its clients share, Smarter APIs and SDKs. If you change a SAM model, the journal’s response envelope, the chat contract, or a route that the document covers, read this page first.
The document covers every family of routes of /api/v1, each one typed by Pydantic models and
tested against the platform’s real responses:
Family |
Routes |
|---|---|
The cli api |
Every route of |
The chat api |
|
The LLMClients’ REST reads |
|
The Guardrails’ REST reads |
|
The Secrets’ REST reads |
|
The MCPClients’ REST reads |
|
The vectorstores’ REST api |
|
The LLMHosts’ REST api |
|
The plugins’ REST reads |
|
The Providers’ REST reads |
|
The Proxies’ list |
|
The accounts’ REST reads |
|
The prompt history |
|
The Smarter Journal |
|
The Orchestrators’ and Vectorsearches’ REST reads |
|
The few routes that the document leaves out are listed in
UNDOCUMENTED_ROUTES, each with the reason: web console
actions, whose clients use manifests instead, passthroughs to LLM providers’ own apis, and routes
that aren’t implemented yet. The families that it leaves out, only the views that the platform’s
unit tests call, are listed in UNDOCUMENTED_URLCONFS.
Every Smarter platform serves its document, at /docs/openapi.json, and renders it with Swagger
UI at /docs/openapi/, which the web console’s sidebar links to as OpenAPI Contract. The
served document lists the platform’s own /api/v1 as its first server, so that the page’s Try
it out sends requests to the platform that you are reading it on. Authorize with an api key, as
Token <api key>.
Where the Contract Lives
The contract is code, in smarter.apps.api.v1.contract:
Module or file |
Role |
|---|---|
The JSON Schema of each SAM kind, from the Pydantic model that the kind’s broker validates manifests with, and the kind’s example manifest, rendered by the cli’s example-manifest view. |
|
|
The committed schemas, one per kind. Each is exactly the |
|
The committed example manifests, one per kind: the |
Pydantic models of the cli responses whose data is not a manifest (apply, validate, get, status, version, whoami, resources, prompt), and of the OpenAPI document itself. The views build their responses from them. |
|
Assembles the OpenAPI document from the SAM models, the
|
|
|
The committed OpenAPI document. |
After changing any of the models, or a route that the document covers, run make api-contract. It runs
manage.py export_api_contract in the smarter-app container, copies the files back to your
working tree, and you commit them.
The tests make the contract hard to break by accident. They fail when:
a committed schema, example, or
openapi.json, is out of date,a committed schema differs from what the
json-schemacommand returns,a kind’s example manifest isn’t valid under its own schema,
a real response isn’t valid under the document’s schema for it: of the cli (apply, get, describe, delete, validate, example-manifest, json-schema, status, version, whoami, resources, prompt/config, prompt, and an error), and of the chat api (a configuration, a prompt as JSON, every event of a streamed prompt, and an error). The prompt tests make real, billable LLM requests,
a route of a covered family, a urlconf in
DOCUMENTED_URLCONFS, is neither in the document nor listed, with a reason, inUNDOCUMENTED_ROUTES,a family of routes that
/api/v1mounts is neither covered nor listed, with a reason, inUNDOCUMENTED_URLCONFS.
What the Document Describes
The cli api
Every cli command is a POST. Each one has a 200 response with the command’s data, and a
default response for errors. A request carries an api key in its Authorization header, as
Token <api key>. example-manifest and json-schema are public, and need no key. The
operations are tagged manifests, deployment, platform and chat.
Every response is journaled. Its body has the journal’s api and metadata, beside the
command’s data, a human-readable message, or an error:
{
"data": {"apiVersion": "smarter.sh/v1", "kind": "Guardrail", "name": "pii_redactor"},
"message": "Guardrail pii_redactor applied successfully",
"api": "smarter.sh/v1",
"metadata": {"command": "apply", "thing": "Guardrail"}
}
A manifest of any kind is SAMManifest: a oneOf of the kinds’ schemas, with a
discriminator on kind. The SAM models declare kind as a plain string, so in the document
each kind’s schema adds "const": "<Kind>" to it. A generated client gets a discriminated union,
for example a TypeScript type in which kind: "Guardrail" narrows the manifest to a Guardrail.
The server url has a platform variable, because every Smarter installation has its own, for
example https://platform.example.com. Its default is https://platform.smarter.sh.
status types the platform’s compute and Redis, but not its Kubernetes cluster, whose
description depends on the infrastructure provider.
The two chat routes need a uid query parameter, a unique identifier of the client, under which
the platform caches the client’s chat session, so that the next prompt continues it.
new_session=true starts a new one. prompt/config/<name>/ returns the
Chat API Contract’s version 2 configuration.
prompt/<name>/ takes {"prompt": "..."}, and returns the request that it sent to the
LLMClient beside the LLMClient’s response: the chat contract’s version 1 response, whose body
is decoded from a JSON string into the completion.
The chat api
The chat api is what Smarter Chat and the SDKs call to chat with an LLMClient. Its requests and
responses are the Chat API Contract’s, version 2, which the
X-Smarter-Capabilities: chat-contract-v2 header asks for, and which is also the default. {id}
is the LLMClient’s numeric id or its hashed id.
POST /llm-clients/{id}/config/takes{"session_key": ...}, empty for a new session, and returns the chat’s configuration, whosesession_keyidentifies the session.POST /llm-clients/{id}/prompt/takes{"session_key": ..., "messages": [...]}, and returns the messages that the prompt added. A failed prompt returns the same response with anerror, and the LLM provider’s http status.
A client that sends Accept: text/event-stream receives the prompt as Server-Sent Events:
progress events while it runs, then one result event, whose response is the JSON that a
non-streaming client receives. OpenAPI 3.1 can’t type the events of a stream, so the document names
their schemas in an x-smarter-events extension of the stream’s media type:
"text/event-stream": {
"schema": {"type": "string", "description": "Server-Sent Events: ..."},
"x-smarter-events": {
"progress": {"$ref": "#/components/schemas/PromptChatApiProgressEvent"},
"result": {"$ref": "#/components/schemas/PromptChatApiProgressResult"}
}
}
The REST reads
The REST api’s views read and write each resource family’s rows, e.g. /api/v1/llm-clients/. The
document publishes only their reads, the GET requests. Clients write through the cli api’s
manifests, which the brokers validate, as the SDKs do.
Their responses are built from each app’s contract models, e.g.
smarter.apps.llmclient.contract.models, which are models of the rows, built from them by
from_model(). Shared parts live with the app that owns them: a resource’s owner in
smarter.apps.account.contract.models, and a plugin’s metadata in
smarter.apps.plugin.contract.models. The models describe the JSON that the views returned
before they were modeled, which clients already read, with two corrections:
canDeleteis computed for the requesting user, for every family. The views called their serializers without the request, so it was always false.An LLMClient’s api key is its token’s public
key_id. It was the token’s primary key, its digest.A chat session is read by the session’s id, like the list’s rows. It was a different row’s id, a PromptHistory’s.
The LLMClients’, Secrets’, accounts’ and journal’s REST views require an api
key of a superuser, and refuse other users with a 403. Other users read their resources with the cli
api’s get and describe. The other families’ views accept any api key, and return only what its
user may read; their actions that change something require the user to own the resource. Each
operation’s description says which.
The accounts’ reads (/accounts/) return the accounts, users, user profiles (each user’s membership
of an account) and account contacts. The prompt history (/prompts/history/) returns the chat
sessions, newest first, and the tool calls and plugin usage of each. A session holds a user’s
conversation, so the history includes only the sessions whose user the requesting user may manage:
their own, their account’s users’ for an account admin, and every one for a superuser. The journal’s
list is paged: page, page_size, search and ordering select the page, and its
pagination describes it. The Proxies’
and vectorstores’ routes exist only when the platform enables them (SMARTER_ENABLE_PROXY,
SMARTER_ENABLE_VECTORSTORE).
A Guardrail’s events, the prompt history, and a catalog’s models in an LLMHost discovery, keep the
snake_case keys that they had, e.g. guardrail_name, session_key and parameter_count, unlike the rest of the REST api, which is
camelCase, because clients already read them.
Besides the reads, the document publishes the actions that the SDKs use: a Guardrail’s dry run,
which changes nothing, an MCPClient’s reconnection and tool call, and a vectorstore’s maintenance. A
tool runs on the MCP server, and may change what the server manages, so only the MCPClient’s owner may
call it. Deploying a vectorstore or an LLMHost creates billable infrastructure. Work that continues in the background
answers 202, e.g. a deployment, or documents that are queued to be loaded, and a new snapshot
answers 201.
Errors
The cli api’s errors are the journal’s error response. The chat api’s are the chat contract’s
PromptChatApiErrorResponse. The REST reads’ are Django REST framework’s {"detail": "..."},
RestApiErrorResponse. A failed connection to an MCP server adds the MCPClient’s connection status
beside its detail. Each operation’s default response names its error’s schema.
Clients
The live Swagger and Redoc pages, and how to generate a client from openapi.json, are on the
user-facing Smarter OpenAPI Contract page.