What compatible actually promises
An OpenAI-compatible API accepts the same HTTP requests that the OpenAI SDKs send. You change the base URL and the key, and the client keeps working. That promise sits at the protocol layer: the path /v1/chat/completions, a bearer token in the Authorization header, a JSON body with model and messages, and a response with choices and usage. Streaming follows the same server-sent events pattern.
It does not promise the same model, the same behaviour, the same limits or the same set of endpoints. The company behind the original API decides what it ships, and every other provider decides independently what to copy. Treat compatibility as a way to avoid rewriting your client code, not as a guarantee that nothing else changes.
The differences, side by side
The table lists the areas that vary across providers and what is true for this API. The right-hand column is a prompt for your own testing when you evaluate any service.
| Area | This API | What to check on any provider |
|---|---|---|
| Model ids | One id: uncensored | Whether your old model name is accepted, mapped or rejected |
| Endpoints | /v1/chat/completions, /v1/responses, /v1/models | Whether every endpoint your code calls exists |
| Modalities | Text only | Image, audio and video support, if you need it |
| Embeddings, fine-tuning | Not offered | Where those calls will go instead |
| Context window | 100,000 tokens, prompt plus completion | Whether the limit covers input only or both |
| Concurrency and rate | Full key: 8 at once, 300 per minute. Trial key: 2 at once, 60 per minute | How limits are measured and how a refusal is reported |
| Tool calling, JSON mode | Supported | Edge cases such as streamed tool calls |
| Content behaviour | Tuned to answer without content refusals for lawful adult use | Moderation layers that change what the model returns |
| Billing | Prepaid credit, per token, no subscription | Minimums, expiry, subscription tiers |
The pricing row for this API is $0.15 per 1M input tokens and $0.50 per 1M output tokens. For any other service, read its current pricing page rather than relying on memory or a third-party summary.
Model ids and behaviour
The largest practical difference is the model itself. With OpenAI you pick from a menu of named models. Here you send "model": "uncensored" and get a single open-weight model run by an independent provider. It is not the official API of any model vendor, and it is not a router that forwards requests to several vendors.
That has two consequences. First, you cannot A/B test model sizes by changing a string; you run one model and tune prompts and sampling around it. Second, prompts written for another model may need small edits, because instruction following, formatting habits and refusal behaviour all differ. This model is tuned to answer without content refusals for lawful adult use, so a prompt that carries long safety preambles written for a stricter model can often be shortened.
One hard limit does not change: no sexual content involving minors.
Endpoints and features that are not there
Compatibility is rarely complete across the whole OpenAI surface. Here the gaps are deliberate: the service handles text and nothing else.
- No image, audio or video generation.
- No embeddings endpoint, so a retrieval pipeline needs a separate embedding provider.
- No fine-tuning.
What is present covers most chat applications: POST /v1/chat/completions, POST /v1/responses, streaming, tool calling with tools and tool_choice, and JSON mode through response_format. Sampling controls such as temperature, top_p, stop, seed and the penalties are passed through to the model.
If your application mixes capabilities, the usual pattern is to keep the original provider for embeddings and images, and send only the text generation you want uncensored to the new base URL. The cost and trade-off analysis works through that split.
Limits, errors and headers
Limits are where migrations surprise people, because they decide how your code should behave under load. This API sets 8 concurrent requests and 300 requests per minute on a full key, 2 and 60 on a trial key, and a maximum body of 8 MB. A request that goes over answers HTTP 429. The error code tells you which limit you hit: rate_limited for requests per minute, concurrency for too many requests at once. A temporary overload also returns 429 and adds Retry-After: 5.
Read the code and the header, not only the status. Retrying a concurrency error immediately makes things worse, while a short wait solves it. A streamed request holds its slot until the stream ends, so concurrency counts open streams, not only calls in flight.
Responses include X-Request-Id, X-Balance-USD, X-RateLimit-Limit-Requests and X-RateLimit-Limit-Concurrency. The balance header is a provider-specific extra: OpenAI SDK code ignores it, but you can read it to track spend. Balance is also available through GET /v1/balance and the OpenAI-style GET /v1/dashboard/billing/credit_grants.
Migration checklist
When you move an application between providers, work through the same short list each time. It catches most surprises before users do.
- Replace the base URL, the key and the model id together, and search the repository for old model names hidden in config files.
- Remove calls to endpoints that do not exist on the new side, and route them to the provider that has them.
- Run your prompt set and compare outputs. Expect different wording and formatting from a different model.
- Test streaming through every proxy and load balancer between your users and the API.
- Add retry logic for 429 that reads the error code and the Retry-After header.
- Log token usage per request, so cost surprises show up in days, not at the end of the month.
A quick compatibility test
Do not trust a feature list, including this one. Run the calls your application makes and compare the results. A short script that exercises the parts you rely on takes a few minutes:
from openai import OpenAI
client = OpenAI(
base_url="https://api.apicompatible.com/v1",
api_key="YOUR_API_KEY",
)
# 1. Plain completion
r = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Reply with the word ready."}],
temperature=0,
seed=7,
)
print("plain:", r.choices[0].message.content)
# 2. JSON mode
j = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Return a JSON object with keys name and age for a fictional person."}],
response_format={"type": "json_object"},
)
print("json:", j.choices[0].message.content)
# 3. Streaming
chunks = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Count from one to five."}],
stream=True,
)
print("stream:", "".join(c.choices[0].delta.content or "" for c in chunks if c.choices))
Add a tool-calling case if your code uses functions, and a long-context case close to your real prompt sizes. Details on each parameter are in the documentation, and the API key page shows how to claim the $0.50 trial credit, which is plenty for this kind of check.
