Release v0.21 – v0.23
v0.21 – v0.23 Release: A Published API Contract, a Node.js SDK, and Chat That Looks Like Your Brand
October 17, 2026 · The Smarter Project
v0.20 was a maintenance release. v0.21 to v0.23, released between October 12 and 14, are about the Smarter API as a product. The platform now publishes a machine-readable contract of its whole API, a second official SDK is built from it, and the work of testing the API from the outside found and fixed a long list of bugs, two of them security bugs.
Along the way, an LLMClient’s manifest gained a styles section, so the chat that your users see can finally look like your product.
| Change | What it means for you |
|---|---|
| OpenAPI contract | One OpenAPI 3.1 document describes every family of routes under /api/v1. Every platform serves it at /docs/openapi/, and you can generate a client from it in any language. |
| SAM JSON Schemas | A committed JSON Schema and example manifest for every manifest kind, versioned with the code. |
| Node.js SDK | A TypeScript SDK with the same resources, verbs and errors as the Python SDK. |
| LLMClient styles | Colors, fonts, avatars and background images for the chat, set in the manifest. |
| API fixes | get ?name= works for every kind, a missing resource is a 404, and apply no longer returns internal state. |
| Two security fixes | The prompt history and the plugin read no longer return other accounts’ data. |
One contract for every Smarter client
Every client of the Smarter API has to know the shape of 25 manifest kinds and of every response. Until now, that knowledge lived only in the running platform. The Python SDK downloaded each kind’s JSON Schema from a live server, the CLI’s types were written by hand, and the live Swagger page was OpenAPI 2, with untyped request and response bodies, so nothing useful could be generated from it.
Now the contract is a set of files in the repository, generated from the same Pydantic models that the platform uses to validate manifests and build its responses:
openapi.json, an OpenAPI 3.1 document of the CLI API, the chat API, and the REST reads of every resource family: LLMClients, Guardrails, Secrets, MCPClients, Vectorstores, LLMHosts, plugins, Providers, Proxies, Orchestrators, Vectorsearches, accounts, the prompt history and the Smarter Journal.sam/<Kind>.schema.jsonandsam/<Kind>.example.json, the JSON Schema and an example manifest of each of the 25 kinds.
Because the contract is committed, a change to the API is a change to a file, and it shows up in a pull request’s diff. You can compare two release tags to see exactly what changed.
Every platform also serves its own copy:
https://<your-smarter>/docs/openapi/renders the document with Swagger UI. The web console’s sidebar links to it as OpenAPI Contract, and Try it out sends requests to the platform you are reading it on.https://<your-smarter>/docs/openapi.jsonis the document itself, with your platform’s own/api/v1as its first server, so a client generated from it matches your platform exactly.
npx openapi-typescript https://<your-smarter>/docs/openapi.json -o smarter.d.ts
A few conventions make generated clients pleasant to use:
- One type for any manifest.
SAMManifestis aoneOfof every kind, with a discriminator onkind, so a generated client narrowskind: "Guardrail"to a Guardrail. - Typed streams. OpenAPI can’t type the events of a
text/event-streamresponse, so the chat API’s streaming prompt names each event’s schema in anx-smarter-eventsextension. - Predictable errors. Every operation names its error’s schema: the Smarter Journal’s error envelope for the CLI API, the chat contract’s error for the chat API, and DRF’s
{"detail": "..."}for the REST reads. - Reads only, for REST. Clients create and change resources through manifests, which the platform validates. The REST families publish their reads, plus the few actions that a manifest can’t express, such as a Guardrail’s dry run or a Vectorstore search.
Why you can trust it
The contract is tested against the platform’s real responses, not written by hand next to them. The test suite checks that the committed files match what the models generate, that every kind’s example manifest is valid under its own schema, and that real responses of every documented operation (including errors, 403s and 404s) are valid under the document. A test also walks every route under /api/v1. A new route can’t ship undocumented: it fails the build until it’s documented, or listed with a reason as deliberately left out.
📖 Smarter OpenAPI Contract · The API Contract in detail
The Smarter Node.js SDK
JavaScript and TypeScript developers had no client for the Smarter API. They wrote HTTP calls by hand, or customized the platform. v0.23 adds the official Node.js SDK, written in TypeScript, with the same features, resources, verbs and errors as the Python SDK, which is the reference implementation of every Smarter SDK.
import { Smarter } from "@smarter.sh/api";
const client = new Smarter(); // SMARTER_API_KEY, or the smarter cli's ~/.smarter/config.yaml
const llmclient = await client.llmClients.find("customer_support");
const response = await client.llmClients.prompt(llmclient.id, "Where is my order #1234?");
console.log(response.text);
- Every resource kind, through the same manifest API as the CLI:
apply,validate,get,describe,delete,deploy,undeployandlogs. - Prompts, multi-turn chats and streams, exactly as Smarter Chat sends them.
- Typed manifests: a TypeScript type of every manifest kind, generated from the platform’s JSON Schemas, and
Manifest.toSam(kind)to validate a manifest against its kind. - Validated responses: every response is checked with a Zod schema, and each type is inferred from its schema, so the two can’t disagree. Unknown fields are kept, so a newer platform doesn’t break an older SDK.
- No HTTP dependency: it uses Node’s built-in
fetch, retries 408, 429, 502 and 504 with the Python SDK’s rules, honorsRetry-After, and masks API keys in its logs. - Same configuration as the CLI: a developer who has run
smarter configureneeds no other setup.
Its tests fail when the SDK calls a route that isn’t in openapi.json, or when openapi.json has an operation that the SDK doesn’t call, so the SDK can’t drift from the platform. It runs on Node.js 22 and later.
The source and releases are at github.com/smarter-sh/smarter-nodejs. The package will be published to npm as @smarter.sh/api.
The Python SDK, smarter-api on PyPI, now generates its models from the same committed contract, instead of downloading schemas from a running platform.
Chat that looks like your product: LLMClient styles
An LLMClient’s manifest had two look-and-feel settings, a logo and a background image, and Smarter Chat never used either of them. Prompt engineers had no way to brand the chat that the workbench and every embedding page show.
In v0.21, spec.config is split in two. prompt (required) is everything the LLMClient is and does. styles (optional) is how its chat looks. Here is the built-in weather LLMClient, trimmed:
apiVersion: smarter.sh/v1
kind: LLMClient
metadata:
name: weather
description: "an llmclient that predicts the weather."
version: 0.1.0
spec:
config:
prompt:
appName: Weather LLMClient
appPlaceholder: Ask me about the weather...
appWelcomeMessage: Welcome to the Weather LLMClient! How can I help you today?
defaultModel: gpt-6-luna
defaultSystemRole: You are a helpful weather forecaster.
provider: openai
deployed: false
styles:
appLogoUrl: https://cdn.smarter.sh/images/logo/smarter-crop.png
appBackgroundImageUrl: https://cdn.smarter.sh/images/llmclients/weather/background.jpg
appBackgroundImageSize: auto
assistantAvatarUrl: https://cdn.smarter.sh/images/llmclients/weather/assistant.png
googleFont: Quicksand
borderRadius: 20px
conversationHeader:
backgroundColor: '#0277bd'
color: '#ffffff'
messageList:
backgroundColor: rgba(225, 245, 254, 0.75)
color: '#01579b'
outgoingMessage:
backgroundColor: '#ffb300'
color: '#3e2723'
- Modeled on the chat’s own components. Smarter Chat is built on chatscope, so
styleshas one section per chatscope component (mainContainer,conversationHeader,messageList,incomingMessage,outgoingMessage,messageInput,buttons,typingIndicator) and one setting per chatscope variable. - Global settings: logo, background image and its size, assistant and user avatars, a Google Font or font stack, font size, and border radius.
- Themes you can copy: every built-in LLMClient and both Stackademy LLMClients have one, such as a dark code editor for
mcp-builder, a chalkboard with a handwriting font formath, and a sunny sky forweather. - Written for prompt engineers. The new styles guide has a labeled diagram of the chat, a short CSS crash course, every setting, and a step-by-step case study.
Security
Styles are written into every page that embeds the LLMClient, so they are validated as plain CSS: colors, lengths, font stacks and Google Font names, with no url(), semicolons, braces or escapes. Image URLs must be https. When you apply or validate a manifest, Smarter requests each image URL and requires an HTTP 200 that isn’t a web page, and it refuses private addresses and validates every redirect. A misspelled setting is an error, not a silently ignored key.
Behavior changes you will notice
These changes came from testing the API as an outside client, through the Python SDK. Each one makes the API behave as its documentation said it would, but a client that worked around the old behavior may notice.
- LLMClient manifests use the new layout.
applyrejects a manifest in the old flatspec.configlayout, with an error that says where each key belongs now, such as Move spec.config.appName to spec.config.prompt.appName.subdomain,customDomain,deployedandallowedOriginsmove intopromptwith everything else. get ?name=works for every kind. The query string never reached the brokers, so everygetreturned all resources and clients filtered by name themselves.- A missing resource is a 404.
describe,delete,deployandundeployof a resource that doesn’t exist answered 503 or 400. applyreturns onlyapiVersion,kindandname. It used to return the broker’s internal state, including a masked API key, the auth header, the caller’s IP address and a cache key. To read a resource, describe it.- Whatever
getlists,describecan show. Built-in resources shared with your account, such as the built-in Guardrails, appeared ingetbut answered 404 ondescribe. - Each plugin kind’s
getreturns only its own kind. It used to return every kind’s plugins, and describing one of them with the wrong kind answered 500. - API key names are snake_case, like every other manifest name, so
getanddescribeagree. Existing keys are renamed, such assmarter-admin-keytosmarter_admin_key. - CLI errors are journaled.
status,version,whoamiandresourcesreturned a bare error body on failure. They now return the Smarter Journal’s error envelope, like every other command. - REST errors are
{"detail": "..."}. Several families answered{"error": "..."}, or plain text, for some failures. canDeleteis correct. The REST reads computed it without the request, so it was alwaysfalse. An LLMClient’s API key is now its public key ID, not its hash.
Security fixes
Writing the contract meant checking every read’s permissions with a user of another account. Two reads failed that check.
- Prompt history. The chat history API returned every account’s chat sessions, tool inputs and tool results to any authenticated user. It now returns your own sessions, your account’s sessions if you are an account admin, and every session only to a superuser.
- Plugin read. While the
ALLOW_API_GETswitch was on,GET /api/v1/plugins/<id>/returned any account’s plugin manifest, such as a SQL plugin’s queries, to any authenticated user. It now answers 404 for a plugin that you may not read.
If you run a multi-account platform, upgrade.
Fixes and operations
- Faster startup. Every Django start paused for about 13 seconds and printed a Redis connection error when the Celery broker wasn’t reachable yet. A needless broker check at import time is gone.
- Undeploy is journaled as undeploy. Every LLMClient undeploy was recorded, and reported in the response, as a deploy.
- Providers API.
GET /api/v1/providers/answered 500. It now lists the active Providers that you may read, and their models. - CLI
getof Account and SqlConnection answered 503. - Prompts with tool calls. The prompt API rejected an assistant tool-call message without
content. It now follows OpenAI’s rule, which allows it. - Account and user-profile reads always answered 500. They work now.
- Example manifests are deterministic. The committed examples differed between macOS and Linux, and the Secret example changed every night at midnight UTC.
- New docs: Smarter on Kubernetes. A new section explains what changes when Smarter runs on Kubernetes instead of Docker: what deploying an LLMClient creates in the cluster and in DNS, how long each step takes, and what to monitor, secure and clean up in production.
Upgrading
- Convert your LLMClient manifests to
spec.config.promptandspec.config.stylesbefore you apply them again. The error message names each key to move. Stored LLMClients are converted for you: the migrations copy each one’s logo and background image URLs into its styles. - API key names change. A data migration renames existing keys to snake_case, such as
smarter-admin-keytosmarter_admin_key. Update any script that looks a key up by name. - Clients that parse error bodies. Error bodies of
status,version,whoamiandresources, and of several REST families, have changed shape (see above). Update the Smarter CLI and the Python SDK with the platform. - The prompt history detail (
GET history/prompt/{id}/) now takes the session’s ID, which is what the list returns, rather than a history row’s ID. manage.py initialize_platformloads the built-in LLMClients with their new styles.
What’s next
With the contract in place, the next releases continue on the same path: v0.24 to v0.26 serve the Smarter Project’s own code and docs over MCP, add rate limiting to the API, and harden the platform’s production configuration.
The full list of changes is in the v0.21, v0.22 and v0.23 changelogs. To try the release, start with the documentation, or browse the code on GitHub.
