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.

ChangeWhat it means for you
OpenAPI contractOne 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 SchemasA committed JSON Schema and example manifest for every manifest kind, versioned with the code.
Node.js SDKA TypeScript SDK with the same resources, verbs and errors as the Python SDK.
LLMClient stylesColors, fonts, avatars and background images for the chat, set in the manifest.
API fixesget ?name= works for every kind, a missing resource is a 404, and apply no longer returns internal state.
Two security fixesThe 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.json and sam/<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.json is the document itself, with your platform’s own /api/v1 as 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. SAMManifest is a oneOf of every kind, with a discriminator on kind, so a generated client narrows kind: "Guardrail" to a Guardrail.
  • Typed streams. OpenAPI can’t type the events of a text/event-stream response, so the chat API’s streaming prompt names each event’s schema in an x-smarter-events extension.
  • 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, undeploy and logs.
  • 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, honors Retry-After, and masks API keys in its logs.
  • Same configuration as the CLI: a developer who has run smarter configure needs 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.

📖 Node.js SDK documentation


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 styles has 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 for math, and a sunny sky for weather.
  • 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.

📖 LLMClient styles


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. apply rejects a manifest in the old flat spec.config layout, with an error that says where each key belongs now, such as Move spec.config.appName to spec.config.prompt.appName. subdomain, customDomain, deployed and allowedOrigins move into prompt with everything else.
  • get ?name= works for every kind. The query string never reached the brokers, so every get returned all resources and clients filtered by name themselves.
  • A missing resource is a 404. describe, delete, deploy and undeploy of a resource that doesn’t exist answered 503 or 400.
  • apply returns only apiVersion, kind and name. 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 get lists, describe can show. Built-in resources shared with your account, such as the built-in Guardrails, appeared in get but answered 404 on describe.
  • Each plugin kind’s get returns 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 get and describe agree. Existing keys are renamed, such as smarter-admin-key to smarter_admin_key.
  • CLI errors are journaled. status, version, whoami and resources returned 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.
  • canDelete is correct. The REST reads computed it without the request, so it was always false. 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_GET switch 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 get of 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.prompt and spec.config.styles before 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-key to smarter_admin_key. Update any script that looks a key up by name.
  • Clients that parse error bodies. Error bodies of status, version, whoami and resources, 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_platform loads 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.