Documentation

Usage & examples.

UAI is an OpenAI-compatible API — drop in your key and start making requests. No SDK changes needed.

Quick start

UAI is fully OpenAI-compatible. Point any existing SDK or tool at https://uai.sh/v1 and replace your API key.

Base URL: https://uai.sh/v1
Auth: Authorization: Bearer uai-YOUR_API_KEY
Try now: uai-try (demo token, one cached response)

curl

Non-streaming request:

curl https://uai.sh/v1/chat/completions \
  -H "Authorization: Bearer uai-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4.1-mini",
    "messages": [{"role": "user", "content": "Say hi in one short sentence."}],
    "stream": false
  }'

Streaming (SSE):

curl https://uai.sh/v1/chat/completions \
  -H "Authorization: Bearer uai-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4.1-mini",
    "messages": [{"role": "user", "content": "Say hi in one short sentence."}],
    "stream": true
  }'

List available models:

curl https://uai.sh/v1/models \
  -H "Authorization: Bearer uai-YOUR_API_KEY"

Python

Install the OpenAI SDK:

pip install openai

Basic request:

from openai import OpenAI

client = OpenAI(
    base_url="https://uai.sh/v1",
    api_key="uai-YOUR_API_KEY"
)

response = client.chat.completions.create(
    model="openai/gpt-4.1-mini",
    messages=[{"role": "user", "content": "Say hi in one short sentence."}],
    stream=False
)

print(response.choices[0].message.content)

Streaming:

stream = client.chat.completions.create(
    model="anthropic/claude-3.5-sonnet",
    messages=[{"role": "user", "content": "Write a poem"}],
    stream=True
)

for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="", flush=True)

Environment variable (recommended):

import os
from openai import OpenAI

# export OPENAI_API_KEY=uai-YOUR_API_KEY
# export OPENAI_BASE_URL=https://uai.sh/v1
client = OpenAI()  # reads env vars automatically

Node.js

Install the OpenAI SDK:

npm install openai

Basic request:

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://uai.sh/v1",
  apiKey: process.env.UAI_KEY,
});

const res = await client.chat.completions.create({
  model: "openai/gpt-4.1-mini",
  messages: [{ role: "user", content: "Say hi in one short sentence." }],
  stream: false,
});

console.log(res.choices[0].message.content);

Streaming:

const stream = await client.chat.completions.create({
  model: "anthropic/claude-3.7-sonnet",
  messages: [{ role: "user", content: "Write a poem" }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || "");
}

Embeddings

POST /v1/embeddings — OpenAI-compatible. Free option: use shared-local/up_mac_ultra_local_ollama/nomic-embed-text or shared-local/up_lenovo_rtx8000_ollama/nomic-embed-text (768-dim, no BYOK needed). BYOK models: text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002, mistral-embed (requires BYOK key).

curl (free, nomic-embed-text via Mac Ultra):

curl https://uai.sh/v1/embeddings \
  -H "Authorization: Bearer uai-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "shared-local/up_mac_ultra_local_ollama/nomic-embed-text",
    "input": "The quick brown fox"
  }'

Python (OpenAI SDK):

from openai import OpenAI

client = OpenAI(base_url="https://uai.sh/v1", api_key="uai-YOUR_API_KEY")

resp = client.embeddings.create(
    model="shared-local/up_mac_ultra_local_ollama/nomic-embed-text",
    input=["Hello world", "Another sentence"]
)
vectors = [d.embedding for d in resp.data]

Node.js (OpenAI SDK):

const resp = await client.embeddings.create({
  model: "shared-local/up_mac_ultra_local_ollama/nomic-embed-text",
  input: ["Hello world", "Another sentence"],
});
const vectors = resp.data.map(d => d.embedding);
Parameters
model — embedding model ID (required)
input — string or array of strings (required)
encoding_format"float" (default) or "base64"
dimensions — reduce output dimensions (text-embedding-3-* only)

Cursor IDE

Cursor works with UAI as a normal OpenAI-compatible endpoint.

Normal setup
  1. Create a key at uai.sh/app/keys.
  2. Open Cursor SettingsModels.
  3. Set OpenAI API Key to your uai-... key.
  4. Enable Override OpenAI Base URL and set it to https://uai.sh/v1.
  5. When Cursor asks for a model, use the full model id from /v1/models or /models.
Model names to use

For OpenAI-family models in Cursor, prefer UAI aliases: uai-gpt41-mini, uai-gpt53-codex, uai-gpt54.

For other providers, use the full catalog id, for example anthropic/claude-opus-4.6.

If openai/gpt-5.3-codex shows Unauthorized User Openai API key, Cursor is trying to use OpenAI directly. Switch the model name to uai-gpt53-codex.

Codex / Agent mode

Start with uai-gpt53-codex.

If Cursor shows We're having trouble finding the resource you requested, switch Network → HTTP Compatibility Mode to HTTP/1.1.

If Cursor Agent still ignores the custom base URL, run node scripts/cursor-proxy.mjs, set base URL to http://localhost:4142/v1, and use model cus-gpt-5.3-codex.

Choosing a model

Pass any model ID from the catalog in the model field. Browse the full list at uai.sh/models.

openai/gpt-4.1-mini

Fast, cheap, great for most tasks

openai/gpt-4o

High quality, multimodal

anthropic/claude-3.7-sonnet

Best for long documents and code

google/gemini-2.0-flash-001

Fast, 1M context window

deepseek/deepseek-r1

Reasoning, open weights

meta-llama/llama-3.3-70b-instruct

Open weights, fast 70B

Free models (with :free suffix) are $0.00/token — great for testing.

Streaming details

SSE format identical to OpenAI's. Each chunk is a data: {...} line; the stream ends with data: [DONE].

Token usage in the final chunk — many SDKs (LangChain, OpenRouter SDK, llama_index) need this:

{
  "model": "openai/gpt-4.1-mini",
  "messages": [{"role": "user", "content": "hi"}],
  "stream": true,
  "stream_options": {"include_usage": true}
}

Last non-DONE chunk will include a populated usage object with prompt/completion/total tokens.

Cancellation — abort the request, the worker stops generating within ~1 s and releases the balance hold:

const ac = new AbortController();
setTimeout(() => ac.abort(), 200);
await client.chat.completions.create(
  { model: "openai/gpt-4.1-mini", messages: [...], stream: true },
  { signal: ac.signal }
);

finish_reason arrives in the last non-DONE chunk: stop | length | tool_calls | content_filter.

Tool calling

Pass tools as an array of {type: "function", function: {...}}. The model returns tool_calls with function.arguments as a JSON-encoded string (matches OpenAI exactly).

{
  "model": "openai/gpt-4.1-mini",
  "messages": [{"role": "user", "content": "What's the weather in Paris?"}],
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Return the current weather for a city",
      "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
      }
    }
  }],
  "tool_choice": "auto"
}

tool_choice variants:

Value Behaviour
"auto"Default. Model decides whether to call.
"none"Force a plain reply. tool_calls will be omitted.
"required"Force at least one tool call. finish_reason: "tool_calls".
{"type":"function","function":{"name":"X"}}Force a call to exactly X.

parallel_tool_calls: false — model returns at most one entry in tool_calls.

When tool_calls is present, message.content is either null or a string — never an object — so OpenAI SDK parsers don't crash.

JSON mode & structured output

Force the model to return parseable JSON:

{
  "model": "openai/gpt-4.1-mini",
  "messages": [
    {"role": "system", "content": "Reply only with valid JSON."},
    {"role": "user", "content": "Give me a JSON object with name and age."}
  ],
  "response_format": {"type": "json_object"}
}

For schema-validated output (where the upstream supports it):

"response_format": {
  "type": "json_schema",
  "json_schema": {
    "name": "person",
    "schema": {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "age":  {"type": "integer"}
      },
      "required": ["name", "age"]
    }
  }
}

Multimodal content

messages[].content accepts BOTH a plain string AND an array of content parts (matches OpenAI's vision API). Required for IDE clients that send screenshots / file attachments.

{
  "model": "openai/gpt-4o",
  "messages": [{
    "role": "user",
    "content": [
      {"type": "text", "text": "What's in this image?"},
      {"type": "image_url", "image_url": {"url": "https://example.com/cat.jpg"}}
    ]
  }]
}

Unknown type values are ignored gracefully (never 5xx).

Free shared-local models

These models run on community hardware (Lenovo RTX 8000, Mac Ultra) and are available at $0/token. A positive account balance is required to use them.

Model ID Hardware Size Best for
shared-local/up_mac_ultra_local_ollama/qwen3-coder:30b Mac Ultra 30B Code generation, refactoring, debugging
shared-local/up_mac_ultra_local_ollama/deepseek-r1:70b Mac Ultra 70B reasoning Math, logic, step-by-step reasoning (streams thinking)
shared-local/up_mac_ultra_local_ollama/gemma3:27b Mac Ultra 27B General chat, summarisation, fast responses
shared-local/up_lenovo_rtx8000_ollama/deepseek-r1:70b Lenovo RTX 8000 70B reasoning Math, logic, complex analysis (streams thinking)
shared-local/up_lenovo_rtx8000_ollama/qwen3:235b-a22b Lenovo RTX 8000 235B MoE Highest quality output, complex instructions (22B active)

Trade-offs: shared capacity (may be slow under load), no SLA, hardware can go offline. DeepSeek R1 models stream a reasoning field with thinking tokens visible in the UAI chat UI.

Errors

UAI returns standard OpenAI-compatible error objects.

HTTP Code Meaning
400 invalid_request_error Malformed body or unsupported parameter
400 context_length_exceeded Prompt larger than the model's context window
400 model_not_available Model ID not in the catalog or not active
401 missing_api_key No Authorization header
401 invalid_api_key Key not found, revoked, or user disabled
401 invalid_authorization_header Header present but not Bearer scheme
402 insufficient_balance Insufficient balance — top up at /app/billing/ (required even for free shared-local models)
404 not_found Endpoint or resource not found
405 method_not_allowed Use the HTTP method listed in Allow:
429 rate_limit_exceeded Slow down — retry after the suggested delay
502 upstream_auth_failed Our key with the upstream provider is invalid (not your key)
502 upstream_insufficient_credit Upstream provider is out of credit on our side
502 upstream_error Upstream returned a non-retryable error
503 free_tier_unavailable Free (:free) model has no free provider endpoint right now — retry shortly or use the paid sibling
503 server_error Worker dependency (D1, KV, upstream) temporarily unavailable
{
  "error": {
    "message": "Available UAI balance ($0.00) is below the estimated prompt cost ($0.0001).",
    "type": "insufficient_balance_error",
    "code": "insufficient_balance"
  }
}

Errors that are NOT supported: /v1/completions (legacy text-completion) returns 404 — UAI only implements /v1/chat/completions, /v1/responses, and /v1/embeddings.