Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
833554e
docs: add the Sanitize API to the Gateway docs
ankur-mittal95 Sep 10, 2026
427d70d
docs: sanitize API is a flat price per repair
ankur-mittal95 Sep 10, 2026
03445ef
docs: move the Sanitize API callout to the top of Reliability
ankur-mittal95 Sep 10, 2026
ee925d1
docs: address review on the Sanitize API page
ankur-mittal95 Sep 10, 2026
7ef86ff
docs: sanitize statuses are fixed and fix_failed
ankur-mittal95 Sep 10, 2026
a674b50
docs: rename the Sanitize API to the Auto-fix API
ankur-mittal95 Sep 10, 2026
cf63bd8
docs: add the Auto-fix API to the OpenUI Lang reliability page
Sep 11, 2026
d89917e
docs: match the page voice in the Auto-fix section
Sep 11, 2026
39611af
docs: smoother prose in the production reliability intro
Sep 11, 2026
affa01e
docs: open the production reliability section in context
Sep 11, 2026
328cfb9
docs: do not name Gateway on the Auto-fix API page
Sep 11, 2026
4bcf17c
docs: the Auto-fix API takes the conversation
Sep 11, 2026
542a97b
Do not say Gateway reads the conversation
Sep 11, 2026
73488f4
docs: the conversation takes the OpenAI chat message shape
Sep 11, 2026
01013f7
docs: the Autofix API takes the OpenAI chat shape
Sep 11, 2026
b7ceb25
docs: drop the Python Autofix snippet for now
Sep 11, 2026
10ee852
docs: list non-streaming support under Limits
Sep 11, 2026
ff00748
docs: shorter Autofix intro that states OpenAI compatibility
Sep 11, 2026
faf1044
docs: drop the gateway cross-reference from the Autofix intro
Sep 11, 2026
a3f2911
docs: show how to detect an invalid generation before calling Autofix
Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 269 additions & 0 deletions docs/content/docs/gateway/api/autofix.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
---
title: Autofix API
description: Fix invalid OpenUI Lang from any model with one API call.
---

The Autofix API repairs invalid OpenUI Lang after a model has generated it. Send the conversation with the generation as the last assistant turn, and the answer carries valid OpenUI Lang. Use it when your application calls a model directly.

**Endpoint:** `POST https://api.thesys.dev/v1/autofix`

**OpenAI compatible.** The request and the answer use the OpenAI chat completion shape. Point an OpenAI SDK at the base URL `https://api.thesys.dev/v1/autofix` and it posts to `/v1/autofix/chat/completions`. Both paths take the same body and answer the same way.

## Detect an invalid generation

Parse the generation with `@openuidev/lang-core` before you call the API. It is the same check the API runs first, so a generation that passes here would come back as `already_valid`. Skip the call when nothing is wrong.

```ts title="lib/detect.ts"
import { createParser } from "@openuidev/lang-core";
import library from "./openui.spec.json";

const parser = createParser(library.schema, library.root);

/** Everything that stops the generation from rendering. Empty means valid. */
export function findErrors(generation: string): string[] {
const { root, meta } = parser.parse(generation);
return [
...meta.errors.map((e) => `${e.code} ${e.component}${e.path}: ${e.message}`),
...meta.unresolved.map((name) => `unresolved: "${name}" is used but never defined`),
...meta.orphaned.map((name) => `orphaned: "${name}" is defined but never used`),
...(meta.incomplete ? ["incomplete: the generation ends mid-statement"] : []),
...(root === null ? ["missing-root: no root element"] : []),
];
}
```

```ts title="server.ts"
import { findErrors } from "./lib/detect";

const errors = findErrors(generation);
if (errors.length === 0) {
render(generation);
} else {
console.warn("invalid generation", errors);
// Send it to the Autofix API, below.
}
```

`parse` never throws. It takes fenced or bare OpenUI Lang and reports prop errors in `meta.errors`, with the codes listed under [Error codes](#error-codes).

## Fix an invalid generation

Point the OpenAI client at `/v1/autofix` and send the generation as the last assistant turn. Fenced or bare OpenUI Lang both work, and text outside the fence is ignored.

The API adds `fix_summary` to the chat completion, which the OpenAI types do not know about, so the example casts the request and the answer.

```ts title="lib/autofix.ts"
import OpenAI from "openai";
import type { ChatCompletion } from "openai/resources/chat/completions";
import library from "./openui.spec.json";

const client = new OpenAI({
apiKey: process.env.THESYS_API_KEY,
baseURL: "https://api.thesys.dev/v1/autofix",
});

type FixError = { code: string; message: string; statementId?: string };
type FixSummary = {
status: "already_valid" | "fixed" | "fix_failed";
fixed_errors: FixError[];
unfixed_errors: FixError[];
};
type AutofixCompletion = ChatCompletion & { fix_summary: FixSummary };

export async function autofix(messages: OpenAI.ChatCompletionMessageParam[], generation: string) {
return (await client.chat.completions.create({
model: "openui/autofix",
messages: [...messages, { role: "assistant", content: generation }],
library,
} as OpenAI.ChatCompletionCreateParamsNonStreaming)) as AutofixCompletion;
}
```

```ts title="server.ts"
import { autofix } from "./lib/autofix";

// Pass the same messages you sent to your model.
const completion = await autofix(messages, generation);

if (completion.fix_summary.status === "fix_failed") {
renderFallback(generation);
} else {
render(completion.choices[0].message.content);
}
```

`openui.spec.json` is the library spec that `openui generate --spec` writes for your components. See [Generate OpenUI Lang](/docs/gateway/generate-openui-lang) for how it is created. Omit `library` to check the generation against the built-in OpenUI chat library.

`model` is accepted so that an SDK client has something to send, and it is ignored: the fix always runs on the same correction model.

### Without an SDK

The plain path takes the same body. Server calls use the API key described in [Authentication](/docs/gateway/authentication).

```ts title="lib/autofix-fetch.ts"
import library from "./openui.spec.json";

type Message = { role: string; content: string | unknown[] };

export async function autofix(messages: Message[], generation: string) {
const response = await fetch("https://api.thesys.dev/v1/autofix", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.THESYS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
messages: [...messages, { role: "assistant", content: generation }],
library,
}),
});

if (!response.ok) {
throw new Error(`Autofix failed: ${response.status}`);
}

return response.json();
}
```

## Request

| Field | Type | Required | Purpose |
| ---------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `messages` | array | Yes | The conversation, in the OpenAI chat message shape. The last turn must be an `assistant` turn whose text is the generation to fix, up to 100,000 characters. The turns before it are the context: only the text of `user`, `assistant`, `system`, and `developer` turns is read, and tool turns and non-text parts are dropped. |
| `library` | object | No | Your library spec, as written by `openui generate --spec`. Without it, the built-in chat library is used. |
| `model` | string | No | Accepted so that an SDK client can name a model, and ignored. The answer always reports `openui/autofix`. |
| `stream` | bool | No | Streaming is not supported. A fix has nothing to send until it is finished, so `stream: true` is a `400`. |

The other OpenAI fields, such as `temperature` and `tools`, are ignored.

## Read the result

The answer is a chat completion. `choices[0].message.content` holds the complete generation, not a patch: replace the model output with it and render it. `fix_summary.status` says what happened.

| `status` | `content` | Errors | Charged |
| --------------- | ------------------------ | ----------------------------------------------------- | ------- |
| `already_valid` | The input, unchanged | `fixed_errors` is empty | No |
| `fixed` | The corrected generation | `fixed_errors` lists what was wrong and is now fixed | Yes |
| `fix_failed` | `null` | `unfixed_errors` lists what is still wrong | Yes |

`fixed_errors` and `unfixed_errors` are always present, and one of them is usually empty.

A fixed generation:

```json
{
"id": "fix_P4TPtYimJFMpSgsViggie",
"object": "chat.completion",
"created": 1789019848,
"model": "openui/autofix",
"choices": [
{
"index": 0,
"finish_reason": "stop",
"message": {
"role": "assistant",
"content": "root = Card([header])\nheader = Header(\"Q3 Results\")"
}
}
],
"usage": { "prompt_tokens": 8014, "completion_tokens": 17, "total_tokens": 8031 },
"fix_summary": {
"status": "fixed",
"fixed_errors": [
{
"code": "unresolved",
"statementId": "followUp",
"message": "reference \"followUp\" is never defined"
}
],
"unfixed_errors": []
}
}
```

A generation that could not be fixed:

```json
{
"id": "fix_dRvfleg31F5g-Amn3v4-W",
"object": "chat.completion",
"created": 1789026253,
"model": "openui/autofix",
"choices": [
{
"index": 0,
"finish_reason": "stop",
"message": { "role": "assistant", "content": null }
}
],
"usage": { "prompt_tokens": 360, "completion_tokens": 146, "total_tokens": 506 },
"fix_summary": {
"status": "fix_failed",
"fixed_errors": [],
"unfixed_errors": [
{
"code": "null-required",
"component": "B",
"path": "/child",
"statementId": "z",
"message": "required field \"/child\" cannot be null"
}
]
}
}
```

When the fix fails, fall back to what your application does for any unusable model output, such as showing the text or a generic error state.

## Error codes

Each entry in `fixed_errors` or `unfixed_errors` names one problem. `code` is always present. `component`, `path`, and `statementId` are present when the validator can name the component type, the property, and the statement that carried the problem.

| Code | Meaning |
| ------------------- | ---------------------------------------------------------------------------------- |
| `unknown-component` | A component name that is not in the library. |
| `missing-required` | A required property was left out. |
| `null-required` | A required property was set to `null`. |
| `type-mismatch` | A property has the wrong type, or a value outside its allowed set. |
| `excess-args` | A component received more arguments than its signature has. |
| `inline-reserved` | `Query()` or `Mutation()` was used inside an expression instead of as a statement. |
| `incomplete` | The generation stopped in the middle of a statement. |
| `unresolved` | A statement is referenced but never defined. |
| `orphaned` | A statement is defined but not reachable from `root`. |
| `missing-root` | The generation has no valid `root` statement. |

These are the same codes the OpenUI SDK reports in the browser, so an application can handle both with one path. See [Observability](/docs/observability) for how they appear in the Thesys Console.

## Limits

- The generation can be up to 100,000 characters.
- The turns before it can hold up to 20 usable turns and 8,000 characters of text in total, counted after tool turns and non-text parts are dropped.
- Only the most recent turns are used. Older turns are dropped first.
- Up to two attempts are made to fix one generation.
- Only non-streaming responses are supported. `stream: true` is a `400`.

## Data

The conversation you send is passed to the model that fixes the generation, and is not stored beyond the request logs.

## Pricing

Each call that runs a fix is charged a flat price from your credits. A generation that is already valid is free. See [Pricing](/docs/gateway/pricing-credits) for the current rate.

`usage` in the answer reports the tokens the fix used, for your own tracking. The charge does not depend on it.

## Errors

The endpoint returns the same error shape as the Chat Completions and Responses APIs.

| Status | Type | When |
| ------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `400` | `invalid_request_error` | The last turn is not an assistant turn with a generation, a limit is passed, `library` is malformed, or `stream` is `true`. |
| `401` | `authentication_error` | The API key is missing or invalid. |
| `429` | `rate_limit_error` | The organization has no credits, or billing is suspended. |
| `500` | `internal_server_error` | The fixing model could not be reached. Nothing is charged. |

## Observability

Autofix calls appear in the Thesys Console under the model name `openui/autofix`. A successful fix counts as a sanitizer correction on the Reliability page, with the same error codes as corrections made during generation.
4 changes: 4 additions & 0 deletions docs/content/docs/gateway/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ full: true
<Card title="Responses API" href="/docs/gateway/api/responses">
Build stateful model interactions with persistent conversations and hosted tools.
</Card>
<Card title="Autofix API" href="/docs/gateway/api/autofix">
Fix invalid OpenUI Lang from any model. Send the raw generation and get back valid OpenUI Lang.
</Card>
</Cards>

<div style={{maxWidth: '900px'}}>
Expand All @@ -78,5 +81,6 @@ full: true
- **Validated generated UI.** Validates generated UI during the stream and fixes malformed syntax, invalid component usage, and schema violations before forwarding the response.
- **Unified API.** Switch between providers and models with minimal code changes.
- **High reliability.** Automatically retries requests with other providers if one fails.
- **Correction on its own.** Keep your model and backend, and send only the generation that needs fixing to the Autofix API.
- **No markup on tokens.** Tokens cost the same as they would from the provider directly, with zero markup, including with Bring Your Own Key (BYOK).
</div>
3 changes: 2 additions & 1 deletion docs/content/docs/gateway/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"---APIs---",
"api/chat-completions",
"api/responses",
"api/conversations"
"api/conversations",
"api/autofix"
]
}
4 changes: 4 additions & 0 deletions docs/content/docs/gateway/pricing-credits.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ The cost of a Gateway request has two parts:

Model usage is billed at the provider's rates without markup. The Gateway API and OpenUI Lang correction behavior remain the same whether you use managed inference or your own provider key.

### Autofix API

An [Autofix API](/docs/gateway/api/autofix) call that runs a fix costs a flat $0.02, deducted from your Gateway Credits. It does not use a plan API call. A generation that is already valid is free.

### Finding model pricing

Model prices vary by provider and model. View the latest rates on the [Thesys pricing page](https://www.thesys.dev/pricing), including:
Expand Down
9 changes: 8 additions & 1 deletion docs/content/docs/gateway/reliability.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ title: Reliability
description: Keep model requests working when providers are unavailable or models return invalid OpenUI Lang.
---

<Callout type="tip" title="New: Autofix API">
Not routing requests through Gateway? Send your model's OpenUI Lang generation to the
[Autofix API](/docs/gateway/api/autofix) and get back valid OpenUI Lang, at a flat price per
fix.
</Callout>

Applications that generate interfaces with AI face two common reliability problems. A model provider may be unavailable, or the model may return output that does not follow the OpenUI Lang instructions and component schemas. The first prevents a response; the second can produce an interface the application cannot render.

Gateway handles each problem differently. It automatically retries the request with other providers and corrects eligible errors in model output while the response streams.
Expand All @@ -12,7 +18,7 @@ Gateway handles each problem differently. It automatically retries the request w
| Problem | What happens | How Gateway responds |
| ------------------------ | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| **Provider unavailable** | An outage, capacity limit, or unavailable route prevents the selected provider from responding. | Gateway retries through a compatible fallback route. |
| **Invalid model output** | The model returns OpenUI Lang with invalid syntax, references, components, properties, or structure. | Gateway corrects eligible errors and continues the stream. |
| **Invalid model output** | The model returns OpenUI Lang with invalid syntax, references, components, properties, or structure. | Gateway corrects eligible errors and continues the stream, or fixes the generation on request through the Autofix API. |

## Recover from provider failures

Expand Down Expand Up @@ -42,6 +48,7 @@ Correction applies only to generated OpenUI Lang. Plain text, arbitrary JSON, ap

Gateway improves the reliability of model access and generated OpenUI Lang. Your application remains responsible for:

- Fallback behavior when a fix fails, whether during a stream or through the Autofix API
- Component behavior and UI error boundaries
- Authentication and permissions
- Tool authorization and input validation
Expand Down
40 changes: 38 additions & 2 deletions docs/content/docs/openui-lang/reliability.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,43 @@ After deploying, open the [Reliability dashboard](https://console.thesys.dev/rel
skill](/docs/mcp#install-via-the-skills-cli-recommended).
</Callout>

## Production reliability with OpenUI Gateway
## Production reliability

Even with a well-tuned schema, prompt, and model, some generations in production will still fail validation. OpenUI offers two ways to repair them automatically, depending on how your application calls the model.

- **[Autofix API](#autofix-api):** your application calls the model itself and sends the generation to the Autofix API for repair.
- **[OpenUI Gateway](#openui-gateway):** OpenUI Gateway calls the model on your behalf and repairs the generation while it streams.

### Autofix API

The [Autofix API](/docs/gateway/api/autofix) repairs OpenUI Lang after your model has generated it, so your application keeps its own model calls, keys, and prompts.

For every generation, the Autofix API:

- **Validates** the generation against your component library.
- **Fixes** unknown components, missing references, and broken structure.
- **Answers** with the complete fixed generation, ready to render.

```ts
const client = new OpenAI({
apiKey: process.env.THESYS_API_KEY,
baseURL: "https://api.thesys.dev/v1/autofix",
});

const completion = await client.chat.completions.create({
model: "openui/autofix",
// The generation to fix is the last assistant turn.
messages: [...messages, { role: "assistant", content: generation }],
library,
});

// completion.fix_summary.status is "already_valid", "fixed", or "fix_failed"
// completion.choices[0].message.content holds the fixed generation.
```

A generation that is already valid is free, and each fix is charged at a flat price. See the [Autofix API reference](/docs/gateway/api/autofix) for the full request and response format and the error codes.

### OpenUI Gateway

OpenUI Gateway sits between your application and the LLM. It validates generated UI and fixes errors as it streams to your application.

Expand Down Expand Up @@ -197,7 +233,7 @@ In production data from our [Reliability benchmark](/blog/generative-ui-benchmar
application. [Install the OpenUI skill](/docs/mcp#install-via-the-skills-cli-recommended).
</Callout>

### Integrations options
#### Integrations options

OpenUI Gateway provides two OpenAI-compatible APIs for this generation flow:

Expand Down
Loading