UAI is an OpenAI-compatible API — drop in your key and start making requests. No SDK changes needed.
UAI is fully OpenAI-compatible. Point any existing SDK or tool at https://uai.sh/v1 and replace your API key.
https://uai.sh/v1Authorization: Bearer uai-YOUR_API_KEYuai-try (demo token, one cached response)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"
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
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 || "");
}
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);
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 works with UAI as a normal OpenAI-compatible endpoint.
uai-... key.https://uai.sh/v1.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.
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.
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.
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.
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.
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"]
}
}
}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).
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.
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.