The short answer
Create the client with two arguments, base_url and api_key, and keep using the same methods you already know. The base URL for this API is https://api.apicompatible.com/v1, the model id is uncensored, and authentication is a standard bearer token that the SDK builds from your key.
from openai import OpenAI
client = OpenAI(
base_url="https://api.apicompatible.com/v1",
api_key="YOUR_API_KEY",
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "Name three uses for a hash map."},
],
)
print(resp.choices[0].message.content)
print(resp.usage)
That is the whole change. The SDK appends /chat/completions to the base URL, so the request lands on POST /v1/chat/completions. If you do not have a key yet, the API key page explains sign-up and the trial credit.
Setting the base URL with environment variables
The official Python SDK reads OPENAI_API_KEY and OPENAI_BASE_URL from the environment when you do not pass them in code. That is useful when the same application runs against different endpoints in development and production, or when a third-party tool builds the client for you and gives you no place to edit constructor arguments.
export OPENAI_BASE_URL="https://api.apicompatible.com/v1"
export OPENAI_API_KEY="YOUR_API_KEY"
python - <<'EOF'
from openai import OpenAI
client = OpenAI()
r = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(r.choices[0].message.content)
EOF
Explicit arguments win over environment variables. If a call goes to the wrong host, check whether some other part of your code builds a client with its own base_url, and whether a .env loader runs before your script reads the variables.
Keep the key out of source control. A key in an environment variable or a secrets manager is easy to rotate; a key in a committed file is not.
The same change in Node
The Node package uses camel case for the same option. Everything else, including the method names and the response shape, matches what you would write against any OpenAI-format endpoint.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.apicompatible.com/v1",
apiKey: process.env.API_KEY,
});
const completion = await client.chat.completions.create({
model: "uncensored",
messages: [{ role: "user", content: "Give me a one-line commit message for a typo fix." }],
});
console.log(completion.choices[0].message.content);
For a raw HTTP check without any SDK, this curl call sends the same request and is handy for separating SDK problems from key problems:
curl https://api.apicompatible.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"uncensored","messages":[{"role":"user","content":"ping"}]}'
Streaming, async and retries
Streaming uses server-sent events, and the SDK turns them into an iterator. Pass stream=True and read the deltas as they arrive. Usage numbers arrive at the end of the stream, so guard for chunks that carry no text.
from openai import OpenAI
client = OpenAI(
base_url="https://api.apicompatible.com/v1",
api_key="YOUR_API_KEY",
)
stream = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Explain idempotency in two sentences."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()
The async client takes the same two arguments. Swap OpenAI for AsyncOpenAI, add await, and use async for on streams.
Two limits matter once you run more than one request at a time. A full key allows 8 concurrent requests and 300 requests per minute, and a trial key allows 2 and 60. A streamed request keeps its concurrency slot until the stream finishes, so a stalled consumer that never drains the stream wastes a slot. Over a limit you get HTTP 429 with the error code rate_limited or concurrency. The SDK retries failed requests a couple of times by default, and you can change that with max_retries on the client. A 429 with a Retry-After: 5 header means temporary overload, so waiting five seconds is the correct response.
Timeouts, client reuse and debugging what was sent
Create the client once and reuse it. Each client keeps its own connection pool, so building a new one for every request wastes time and makes streaming code harder to reason about. Both options below are set on the constructor and apply to every call made through that client.
from openai import OpenAI
client = OpenAI(
base_url="https://api.apicompatible.com/v1",
api_key="YOUR_API_KEY",
timeout=60.0,
max_retries=2,
)
Long generations can take a while, so a very short timeout produces errors that look like service faults. Choose a timeout that fits your longest expected answer, and raise it for large outputs.
When you cannot tell which URL or key a request used, turn on the SDK's debug logging. Recent versions of the Python package read an OPENAI_LOG environment variable, and setting it to debug prints the request line and response status. Check the current SDK documentation for the exact behaviour in your version, and remove the variable afterwards, since logs can contain sensitive data. The printed host should be api.apicompatible.com. If it shows something else, a different client instance or an environment variable is overriding your setting.
A last habit that saves time: keep the base URL, key and model id in one config module. When all three live in one place, switching endpoints becomes a single edit and a single review.
Mistakes that break the first call
Most failed first calls come from a short list of causes. Work through it in order before you suspect the service.
The base URL is missing /v1 or has a stray path
The SDK adds only the endpoint name, so the base URL must end at /v1. Using https://api.apicompatible.com without the version segment, or pasting the full /v1/chat/completions path, usually produces a not-found error because the final URL ends up wrong. Print client.base_url if you are unsure what the SDK resolved.
The model id is still a name from another provider
This API serves one model and its id is uncensored. A leftover model name from a previous setup is a frequent cause of errors, and it is easy to miss when the name sits in a config file instead of the code. Search your project for the old string.
The key comes from the wrong place
A key from another provider will not work here, and the reverse is also true. If you set both OPENAI_API_KEY and an explicit api_key, the explicit value is used. Check which one reached the client.
You call an endpoint this API does not offer
The API is text only. It exposes /v1/chat/completions, /v1/responses and /v1/models. There are no image, audio or embeddings endpoints and no fine-tuning, so code paths that use them have to stay on their original provider. The cost and trade-off analysis covers when that split makes sense.
The payload is too large
The maximum request body is 8 MB. Long contexts are fine up to 100,000 tokens in total for prompt plus completion, but base64 blobs or huge pasted files can hit the body limit first.
Checking the connection and your balance
Before debugging application code, call GET /v1/models. A clean list means the key and URL are right. Every response also carries an X-Balance-USD header, which lets you watch spend without a separate call. With the Python SDK, use with_raw_response to read headers:
from openai import OpenAI
client = OpenAI(
base_url="https://api.apicompatible.com/v1",
api_key="YOUR_API_KEY",
)
raw = client.chat.completions.with_raw_response.create(
model="uncensored",
messages=[{"role": "user", "content": "ping"}],
)
print(raw.headers.get("X-Balance-USD"))
print(raw.headers.get("X-Request-Id"))
completion = raw.parse()
print(completion.choices[0].message.content)
Keep the X-Request-Id value when you contact support, because it identifies the exact request. Failed or refused requests are not charged, so a bad call costs nothing. Pricing is $0.15 per 1M input tokens and $0.50 per 1M output tokens from prepaid credit; the pricing page has the details and the documentation lists every parameter.
