Documentation
One API for every uncensored model.
Open Uncensored speaks the OpenAI chat completions protocol. Point any OpenAI SDK at our base URL, pick a model id from the catalogue, and the router picks the provider.
Start here
Up and running in a minute.
- 01
Get a key
Buy credits and copy theou-key. Until the hosted endpoint opens, run the router yourself (see self hosting) with your ownOU_API_KEY. - 02
Pick a model
Browse models and copy its id. Check its refusal fingerprint and loop rate first. - 03
Send a request
Any OpenAI SDK works. Add aroutingobject when you care which provider serves it.
curl https://openuncensored.com/api/v1/chat/completions \
-H "Authorization: Bearer $OU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "venice-uncensored",
"messages": [{"role": "user", "content": "Explain how SQL injection works."}],
"routing": {"strategy": "quality"}
}'Hosted endpoint
Everything on this page works today against a self hosted router: swap the base URL forhttp://localhost:8080/v1.Security
Keys and auth.
Send your key as a bearer token: Authorization: Bearer <key>. A missing or wrong key returns 401 with code invalid_api_key.
Hosted keys start with ou- and come from the credits page. Each key carries a prepaid USD balance. It is shown once, and we store only its SHA-256 hash. Buy more credits for the same key by ticking “Add to a key I already have”.
On a self hosted router, OU_API_KEY holds the accepted keys (comma separated for several). Keys are compared in constant time. If OU_API_KEY is unset the router runs open, which is only safe on localhost: anyone who can reach the port can spend your provider keys.
Browser use
The router only accepts browser requests from origins listed inOU_CORS_ORIGINS (default: pages served from localhost). Never ship a server key inside client side code.POST /v1/chat/completions
Chat completions.
Fully OpenAI compatible, streaming and non streaming. Samplers outside the OpenAI spec such as top_k, min_p and repetition_penalty are forwarded to the provider untouched, because those matter for the models this router serves.
| Field | Type | Description |
|---|---|---|
| model | string | A model id from GET /v1/models. Required. |
| messages | array | Chat messages, at least one. Tool calls and tool results pass through. |
| stream | boolean | Server sent events when true. Default false. |
| temperature, top_p, max_tokens | number | Standard sampling controls. |
| tools, tool_choice | array, object | OpenAI tool calling. Providers flagged as tool capable are tried first. |
| response_format, seed, stop | various | Forwarded as is. |
| routing | object | Router extension. See routing. |
Response
The upstream body, plus usage when the provider left it out (marked usage_estimated) and an ou object describing how the request was served. The same facts arrive as headers: X-Request-ID, X-OU-Provider, X-OU-Upstream-Model, X-OU-Strategy, X-OU-Attempts and X-OU-Latency-Ms.
{
"id": "chatcmpl-8f2c",
"object": "chat.completion",
"model": "venice-uncensored",
"choices": [
{"index": 0, "message": {"role": "assistant", "content": "SQL injection happens when…"}, "finish_reason": "stop"}
],
"usage": {"prompt_tokens": 18, "completion_tokens": 412, "total_tokens": 430},
"ou": {
"request_id": "req_4f1c09a2b7d34e81c0aa",
"provider": "venice",
"upstream_model": "venice-uncensored",
"strategy": "quality",
"latency_ms": 2140.2,
"cost_usd": 0.000374,
"usage_estimated": false,
"attempts": [
{"provider": "venice", "upstream_model": "venice-uncensored", "outcome": "ok", "status_code": 200, "latency_ms": 2140.2}
]
}
}GET /v1/models
The catalogue.
| Endpoint | Query | Returns |
|---|---|---|
| GET /v1/models | ?available=true | Every model with its providers and eval scores. The flag hides models no healthy, configured provider can serve right now. |
| GET /v1/models/{id} | none | One model with per provider live metrics, pricing, rate limits and circuit breaker state. |
| GET /metrics | none | TTFT, tokens per second, latency percentiles and error rate over the rolling window, plus lifetime tokens and cost. |
| GET /health | ?deep=true | Liveness and provider status (ok, degraded, unavailable). Deep probes every configured upstream and needs a key. |
GET https://openuncensored.com/api/v1/models/venice-uncensored
{
"id": "venice-uncensored",
"object": "model",
"owned_by": "venice-ai",
"base_model": "mistralai/Mistral-Small-24B-Instruct",
"abliteration_method": "finetune",
"hf_repo": null,
"modality": "text",
"context_length": 131072,
"tags": ["general", "privacy"],
"eval_scores": {"agentic_stability": 0.7, "tool_call_validity": 0.9, "capability_accuracy": 0.78},
"quality_score": 0.64,
"available": true,
"providers": [
{"name": "venice", "status": "online", "configured": true, "upstream_model": "venice-uncensored", "quality_score": 0.64},
{"name": "openrouter", "status": "degraded", "configured": true, "upstream_model": "cognitivecomputations/dolphin-mistral-24b-venice-edition", "quality_score": 0.64}
]
}Router extension
Routing strategies.
Each model is served by one or more providers. The router ranks them with a strategy, tries them in order and falls back to the next one when a provider fails before the first streamed byte. Set the strategy per request with the routing body field or the X-OU-Strategy header; the body wins when both are present. The server default is OU_ROUTING_STRATEGY, which defaults to quality.
| Strategy | Ranks by | Notes |
|---|---|---|
| quality | eval scores | Highest eval score first. Providers without eval data count as the median, not best or worst. |
| speed | live latency | Lowest average time to first token for streams, total latency otherwise, from the rolling metrics window. |
| cost | price | Cheapest for this prompt plus max_tokens (512 assumed when unset). |
| random | nothing | Shuffled. Useful for spreading load or A/B tests. |
| Field | Type | Description |
|---|---|---|
| routing.strategy | string | quality, speed, cost or random. |
| routing.only | string[] | Only try these providers. |
| routing.ignore | string[] | Never try these providers. This is how you exclude a host today. |
| routing.allow_fallbacks | boolean | Try the next provider when the chosen one fails. Default true. |
{
"model": "huihui-qwen3.8-27b-abliterated",
"messages": [{"role": "user", "content": "…"}],
"routing": {
"strategy": "cost",
"only": ["featherless", "nanogpt"],
"ignore": ["chutes"],
"allow_fallbacks": true
}
}Providers that fail three times in a row go offline for 60 seconds (OU_BREAKER_THRESHOLD, OU_BREAKER_COOLDOWN). An upstream 429 cools that provider down for its Retry-After, or 30 seconds when none is given. Requests the upstream rejects as invalid (400, 413, 422) are relayed and never retried.
Coming next
Route by what it refuses.
PlannedThe same model can behave differently on different hosts: a platform system prompt, a moderation classifier or an aggressive quantisation all change what comes back. These filters will route only to model and provider pairs whose measured behaviour meets your bar.
| Field | Type | Description |
|---|---|---|
| routing.require_openPlanned | string[] | Categories that must stay open. One of cyber bio chem weapons drugs adult violence hate selfharm privacy fraud political. |
| routing.max_refusalPlanned | number | Highest refusal rate allowed in each required category, 0 to 1. |
| routing.max_loop_ratePlanned | number | Skip pairs whose agentic doom loop rate is above this, 0 to 1. |
| routing.no_loggingPlanned | boolean | Only providers that state they keep no prompt or completion logs. |
{
"model": "huihui-qwen3.8-27b-abliterated",
"messages": [{"role": "user", "content": "…"}],
"routing": {
"strategy": "quality",
"ignore": ["chutes"],
"require_open": ["cyber", "bio"],
"max_refusal": 0.05,
"max_loop_rate": 0.2,
"no_logging": true
}
}Today
Exclusion by provider already works withrouting.ignore and routing.only. Per category numbers live on each model page.stream: true
Streaming responses.
Streams are server sent events in the OpenAI chunk format, ending with data: [DONE]. Fallback to another provider only happens before the first byte reaches you. After that, an upstream failure arrives in band as an OpenAI style error event so your client can surface it. The router tolerates up to 60 seconds of silence mid stream (OU_STREAM_TIMEOUT).
data: {"id":"chatcmpl-8f2c","choices":[{"index":0,"delta":{"content":"SQL"}}]}
data: {"id":"chatcmpl-8f2c","choices":[{"index":0,"delta":{"content":" injection"}}]}
data: {"error": {"message": "upstream closed the connection", "type": "upstream_error", "param": null, "code": null}}
data: [DONE]OpenAI error format
When things go wrong.
Every error uses OpenAI's shape: {"error": {"message", "type", "param", "code", "metadata"}}. metadata carries per provider details, such as why each one was skipped.
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | The body failed validation. |
| 400 | invalid_routing_options | Unknown strategy or provider, or only/ignore exclude every provider. |
| 401 | invalid_api_key | Missing or wrong bearer token. |
| 404 | model_not_found | No model with that id. |
| 4xx | upstream_rejected_request | The provider rejected the request itself. Relayed as is, never retried. |
| 429 | rate_limit_exceeded | Every provider for the model is rate limited. Honour Retry-After. |
| 502 | all_providers_failed | Each provider was tried and failed. |
| 503 | no_provider_available | Every provider is unconfigured or offline. |
| 503 | not_ready | The router is still starting up. |
| 500 | internal_error | A router bug. Check the router logs. |
MIT licensed
Run it yourself.
The router is a FastAPI app. Bring your own provider keys, they are read from the environment variables named by api_key_env in configs/providers.yaml.
git clone https://github.com/DhruvAtreja/open-uncensored
cd open-uncensored
pip install -e .
# provider keys, named by api_key_env in configs/providers.yaml
export FEATHERLESS_API_KEY=…
export VENICE_API_KEY=…
export OPENROUTER_API_KEY=…
# require a bearer token before exposing the port
export OU_API_KEY=$(openssl rand -hex 24)
export OU_CORS_ORIGINS=https://openuncensored.com
ou-router --host 0.0.0.0 --port 8080| Variable | Default | Purpose |
|---|---|---|
| OU_API_KEY | unset | Accepted bearer tokens, comma separated. Unset means open, local use only. |
| OU_HOST, OU_PORT | 127.0.0.1:8080 | Bind address. Also --host and --port. |
| OU_ROUTING_STRATEGY | quality | Default strategy: quality, speed, cost or random. |
| OU_CORS_ORIGINS | localhost | Origins allowed to call the API from a browser, or *. |
| OU_REQUEST_TIMEOUT | 300 | Non streaming upstream timeout, seconds. |
| OU_STREAM_TIMEOUT | 60 | Longest silence tolerated while streaming, seconds. |
| OU_CONNECT_TIMEOUT | 10 | Upstream connect timeout, seconds. |
| OU_METRICS_WINDOW | 100 | Requests per rolling metrics window. |
| OU_BREAKER_THRESHOLD | 3 | Consecutive failures that take a provider offline. |
| OU_BREAKER_COOLDOWN | 60 | Seconds a provider stays offline. |
| OU_HEALTH_INTERVAL | 0 | Seconds between active health checks. 0 is off. |
| OU_LOG_LEVEL | INFO | Also --log-level. --reload restarts on code changes. |
| OU_PROVIDERS_CONFIG, OU_MODELS_CONFIG | configs/ | Alternative YAML files. |
In models.yaml each model can carry upstream_ids (the name a provider expects), pricing, eval_scores and provider_eval_scores for hosts whose quantisation changes quality.
ou-eval
Run the evals yourself.
Every number on this site comes from the same harness you can run locally. Five benchmarks: capability, agentic, tool_call, refusal and divergence. Results land in results/{model}/{provider}/{timestamp}.json. Responses to harmful probes are scored and discarded, never written to disk.
# run every benchmark against one model on one provider
ou-eval --model venice-uncensored --provider openrouter \
--api-model cognitivecomputations/dolphin-mistral-24b-venice-edition \
--benchmarks all --trials 2
# compare against a live base model run
ou-eval --model huihui-qwen3.5-27b-abliterated --provider featherless \
--base-model Qwen/Qwen3.5-27B --base-provider featherless
# fold every results/ file into leaderboard.json
cd src && python -m eval.report --output ../leaderboard/leaderboard.json| Flag | Default | Purpose |
|---|---|---|
| --model / --provider | required | Model id and provider name from configs/. |
| --benchmarks | all | Comma separated subset. |
| --api-model | --model | Model name the provider expects, when it differs. |
| --base-url, --api-key-env | config | Point at an ad hoc endpoint. |
| --base-model, --base-provider | models.yaml | Run the base model live for retention and divergence. |
| --base-accuracy | none | Known base capability accuracy, skips the live base run. |
| --trials | 2 | Agentic runs per scenario, 1 to 20. |
| --tool-mode | auto | text (JSON in prompt), native (tools parameter) or auto. |
| --limit | all | Only the first N items per benchmark. |
| --concurrency, --rpm | 4, provider | Parallel requests and requests per minute. |
| --timeout, --retries | 180, 3 | Per request timeout and retries with backoff. |
| --temperature, --max-tokens | per bench | Override sampling. Raise max tokens for reasoning models. |
| --results-dir, --no-save | ./results | Where results go, or skip writing them. |
How each score is computed is on the methodology page.