The AI SDK with an uncensored model

createOpenAICompatible with our base URL, then generateText, streamText and tool() on an uncensored build: which builds take tool calls, the usage fields, a Next.js route handler, the error codes, and what one call costs at the live price.

Steps checked November 2, 2026 · prices as of November 9, 2026

The AI SDK’s OpenAI-compatible provider reaches every model on this catalog with one createOpenAICompatible call. This page has the provider, generateText, streamText and tool() on an uncensored build, which builds take tool calls, a Next.js route handler, the error codes, and what one call costs at the live price.

Install

Two packages: ai, the SDK, and @ai-sdk/openai-compatible, the provider for any endpoint that speaks the OpenAI Chat Completions format. zod is for the tool example further down.

shell
1pnpm add ai @ai-sdk/openai-compatible zod
2# or
3npm install ai @ai-sdk/openai-compatible zod

The key comes from the console, starts with sk-ullm-, is shown once and spends a prepaid balance that starts at $10. Put it in an environment variable; the code never holds it.

shell
1export UNCENSOREDLLM_API_KEY="sk-ullm-live-…"

The provider

createOpenAICompatible takes a name, our baseURL and the apiKey. The base URL ends in /v1; the SDK adds /chat/completions. includeUsage puts the token counts on streamed answers as well, which the cost table below depends on.

provider.ts
1import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
2
3export const uncensored = createOpenAICompatible({
4 name: 'uncensoredllm',
5 baseURL: 'https://api.uncensoredllm.ai/v1',
6 apiKey: process.env.UNCENSOREDLLM_API_KEY,
7 includeUsage: true, // usage in streaming responses too
8});
name
uncensoredllm
any label; it names the provider in providerOptions
baseURL
https://api.uncensoredllm.ai/v1
ends in /v1; nothing after it
apiKey
process.env.UNCENSOREDLLM_API_KEY
sent as Authorization: Bearer
model
uncensored('glm-5.3-uncensored')
any id from the models page; provider.chatModel(id) is the same call

Anything else that speaks this format, the OpenAI SDKs included, uses the same base URL and key.

generateText

provider('model-id') is a chat model; pass it as model. The result carries text, finishReason and usage with inputTokens, outputTokens and totalTokens: the numbers the request was billed on.

generate.ts
1import { generateText } from 'ai';
2import { uncensored } from './provider';
3
4const { text, usage, finishReason } = await generateText({
5 model: uncensored('glm-5.3-uncensored'),
6 prompt: 'Explain how a lock pick works, step by step.',
7});
8
9console.log(text);
10console.log(usage.inputTokens, usage.outputTokens, usage.totalTokens, finishReason);

streamText

streamText returns at once; textStream yields the pieces as the model writes them, and result.usage is a promise that resolves when the stream ends.

stream.ts
1import { streamText } from 'ai';
2import { uncensored } from './provider';
3
4const result = streamText({
5 model: uncensored('glm-5.3-flash-uncensored'),
6 prompt: "Write a villain's monologue.",
7});
8
9for await (const piece of result.textStream) process.stdout.write(piece);
10
11const usage = await result.usage; // resolves when the stream ends; needs includeUsage on the provider
12console.log(usage.inputTokens, usage.outputTokens);

In a Next.js route handler, return result.toTextStreamResponse() for plain text or result.toUIMessageStreamResponse() for the useChat hook.

route.ts
1// app/api/chat/route.ts
2import { streamText } from 'ai';
3import { uncensored } from '@/lib/provider';
4
5export async function POST(req: Request) {
6 const { prompt } = (await req.json()) as { prompt: string };
7 const result = streamText({
8 model: uncensored('glm-5.3-uncensored'),
9 prompt,
10 });
11 return result.toTextStreamResponse();
12}

Tool calls

tool() takes a description, an inputSchema and an execute function; stopWhen: isStepCount(5) lets the SDK run the tool and send the result back for up to five steps. steps holds the tool calls and results of each one.

tools.ts
1import { generateText, isStepCount, tool } from 'ai';
2import { z } from 'zod';
3import { uncensored } from './provider';
4
5const { text, steps } = await generateText({
6 model: uncensored('glm-5.3-uncensored'),
7 tools: {
8 weather: tool({
9 description: 'Get the weather in a location',
10 inputSchema: z.object({
11 location: z.string().describe('The location to get the weather for'),
12 }),
13 execute: async ({ location }) => ({ location, temperature: 72 }),
14 }),
15 },
16 stopWhen: isStepCount(5), // keep calling tools for up to 5 steps
17 prompt: 'What is the weather in San Francisco?',
18});
19
20const toolCalls = steps.flatMap((step) => step.toolCalls);
21console.log(toolCalls.length, text);

A tool call needs a build whose base model was trained for it. The catalog publishes tools among a build’s supported parameters once every route of it takes tool calls; 42 listed builds publish it on 2026-11-09, and the agent tools page tables them all. The builds on this page do not publish the field on 2026-11-09; pick a build from that table for tool work and keep these for text.

nemotron-3-ultra-550b-a55b-uncensored
takes tool calls
Nemotron 3 Ultra 550B Uncensored
abliterated-model-large-flash
takes tool calls
Abliterated Large Flash
abliterated-model-large
takes tool calls
Abliterated Model Large
abliterated-model-large-v2
takes tool calls
Abliterated Model Large V2

The gateway also runs tools of its own: web search, web fetch, a remote MCP server and a code sandbox, declared in the request’s tools array and run inside the one request, on the agent tools page.

What a call costs

One chat call of 2,000 input and 400 output tokens, the shape of a short conversation with a system prompt, on four builds at the catalog prices on 2026-11-09:

ModelOne call1,000 calls
GLM-5.3 Uncensored$0.0009$0.90
GLM-5.3 Abliterated$0.0061$6.11
DeepSeek V4.1 Flash Abliterated$0.0024$2.38
GLM-5.3 Flash Uncensored$0.00036$0.36

usage.inputTokens and usage.outputTokens on each result are the counts the charge was computed from; multiply by the per-million prices below to reconcile a bill.

Uncensored models and their prices per million tokens
GLM-5.3 Uncensoredglm-5.3-uncensoredUncensored$0.282$0.846
GLM-5.3 Abliteratedglm-5.3-abliteratedAbliterated$2.1825$4.365
DeepSeek V4.1 Flash Abliterateddeepseek-v4.1-flash-abliteratedAbliterated$0.8488$1.6975
GLM-5.3 Flash Uncensoredglm-5.3-flash-uncensoredUncensored$0.1053$0.3685

Prices per million tokens, read from the live catalog on 2026-11-09.

Check it

The list call is free and proves the key and base URL; it returns exactly the ids the key can call.

shell
1curl https://api.uncensoredllm.ai/v1/models -H "Authorization: Bearer sk-ullm-live-…"

Every answer carries x-uplink-request-id; a non-streamed answer carries x-uplink-charge, what it cost in USD. The console’s usage page lists both, and the docs page has the rest of the API.

Errors and fixes

  • 401 invalid_api_key: the key is wrong or revoked. Copy it again from the console and check the variable name the provider reads.
  • 402 insufficient_balance: the balance does not cover the request. Top up in the console. 402 monthly_limit_reached means the key hit the monthly spend limit you set on it.
  • 403 scope_denied: the key is limited to certain models and this is not one of them.
  • 404 model_not_found: the id in uncensored(‘…’) is not on the models page. Check the spelling; display names are not ids.
  • 429 rate_limited: the key hit its rate limit. The response carries retry-after.
  • A request that fails before the model answers is not charged.

Questions

@ai-sdk/openai-compatible sends Chat Completions, which every listed build answers. The catalog also lists /v1/responses on the builds on this page, so the @ai-sdk/openai package, whose default openai(id) uses the Responses API, reaches them too; openai.chat(id) keeps it on Chat Completions.

Both reach the models. The compatible provider is written for endpoints like this one: Chat Completions by default, includeUsage for streamed usage, and providerOptions for extra request fields. @ai-sdk/openai works through its .chat() factory. See the OpenAI SDK guide.

Yes. OpenCode runs on the AI SDK and takes a custom OpenAI-compatible provider in its config; the same base URL, key and model ids apply. See the OpenCode guide.

Call streamText in the POST handler and return result.toTextStreamResponse() for plain text, or result.toUIMessageStreamResponse() when the page uses useChat. The route example above is the whole handler.

Every model above is on the uncensored model list with its live price per million tokens. Per-token pricing sets the whole catalog side by side, and the family directory groups the builds by base model.

Get a keyPrepaid from $10, no subscription.

New models and price changes, once a month.

New uncensored builds with their prices, price moves, and what the daily model check found.

Join on Telegram