One OpenAI-compatible endpoint. Send model: "auto" and ZeroOptimize™ routes the request to the best free model available at that moment, with automatic failover. Inference cost is $0 on every plan; plans differ by daily call volume.
https://www.zerolimitai.com/api/v1Authorization: Bearer zlai_…JSON · SSE when stream: trueopenai SDKs, LangChain, Cline, Continue, Aider…Always use the www host. The apex domain redirects with a 307 and some HTTP clients drop the Authorization header on the hop.
Every request carries your key as a Bearer token. Create keys in Developer settings. A free account gets one key for a 7-day evaluation at 100 calls/day; after that the key returns 402 upgrade_required until the account has a plan. Annual accounts share a 2,000 calls/day quota and Lifetime accounts a 5,000 calls/day one. Keys are stored as bcrypt hashes and shown once.
Authorization: Bearer zlai_xxxxxxxxxxxxxxxxxxxxxxxx
Send a conversation, get a completion. /chat is the same handler under a shorter path.
| Parameter | Type | Description |
|---|---|---|
messagesrequired | array | { role, content } objects; role is "system", "user" or "assistant". |
model | string | Default "auto": ZeroOptimize™ picks the best free model for this request. Pass an id from GET /models to try that model first, with the rest of the chain as failover. Any other id — an OpenAI or Anthropic model name, one we retired — is routed by ZeroOptimize instead of failing, so an existing app works by changing base_url alone. It is never silent: the response's model field reports which model actually answered and X-ZeroOptimize-Substituted-From carries the id you asked for. |
stream | boolean | Default false. When true, the response is text/event-stream with OpenAI chat.completion.chunk events, a final chunk with finish_reason ("stop", or "length" when max_tokens cut it), then data: [DONE]. |
agentId | string | Optional. Id of one of your ZeroLimitAI agents. Its system prompt is prepended unless your messages already start with a system message. Agents are created in the app. |
Sampling: temperature (0–2), top_p (0–1), stop (string or up to 4), seed, presence_penalty and frequency_penalty (−2–2) are passed to the model's provider. max_tokens (or max_completion_tokens, 1–32,768) caps the visible answer and sets finish_reason: "length" when it cuts. Out-of-range values return 400 invalid_param.
Send OpenAI tools (and, if you want, tool_choice and parallel_tool_calls). The request goes to the best free model that supports tools, with the same failover, and tool_calls come back in the OpenAI shape, streaming included. Return results as role: "tool" messages with the matching tool_call_id. Pinning a model without tool support returns 400 invalid_param; max_tokens is not applied to tool requests.
from openai import OpenAI
import json
client = OpenAI(base_url="https://www.zerolimitai.com/api/v1", api_key="zlai_xxxxxxxxxxxxxxxxxxxxxxxx")
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
},
}]
messages = [{"role": "user", "content": "What's the weather in Madrid?"}]
first = client.chat.completions.create(model="auto", messages=messages, tools=tools)
call = first.choices[0].message.tool_calls[0]
args = json.loads(call.function.arguments) # {"city": "Madrid"}
messages += [first.choices[0].message,
{"role": "tool", "tool_call_id": call.id, "content": json.dumps({"temp_c": 21})}]
final = client.chat.completions.create(model="auto", messages=messages, tools=tools)
print(final.choices[0].message.content)curl https://www.zerolimitai.com/api/v1/chat/completions \
-H "Authorization: Bearer zlai_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"messages": [
{ "role": "user", "content": "What is the capital of France?" }
]
}'{
"id": "chatcmpl-9d2f6c1e-…",
"object": "chat.completion",
"created": 1758300000,
"model": "qwen/qwen3.8-27b:free",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "The capital of France is Paris." },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 14, "completion_tokens": 9, "total_tokens": 23 }
}
# Response headers
X-Request-Id: chatcmpl-9d2f6c1e-…
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 9987
X-RateLimit-Reset: 1758326400model is whatever the router used — it changes with the daily ranking and with failover. Do not hardcode it.
from openai import OpenAI
client = OpenAI(
base_url="https://www.zerolimitai.com/api/v1",
api_key="zlai_xxxxxxxxxxxxxxxxxxxxxxxx",
)
stream = client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "Tell me a joke."}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="", flush=True)curl https://www.zerolimitai.com/api/v1/chat \
-H "Authorization: Bearer zlai_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"agentId": "YOUR_AGENT_ID",
"messages": [{ "role": "user", "content": "Summarise what you know about my project." }]
}'"auto" plus the live chain, in the OpenAI /v1/models format. Ids are provider ids (for example qwen/qwen3.8-27b:free) and change as the ranking does — read them here rather than hardcoding.
curl https://www.zerolimitai.com/api/v1/models \ -H "Authorization: Bearer zlai_xxxxxxxxxxxxxxxxxxxxxxxx"
{
"object": "list",
"data": [
{ "id": "auto", "object": "model", "created": 1758300000, "owned_by": "zerolimitai" },
{ "id": "qwen/qwen3.8-27b:free", "object": "model", "created": 1758300000, "owned_by": "openrouter" },
{ "id": "inclusionai/ling-3.0-flash-fin:free", "object": "model", "created": 1758300000, "owned_by": "openrouter" },
{ "id": "openai/gpt-oss-120b", "object": "model", "created": 1758300000, "owned_by": "groq" }
]
}API calls today against your daily quota (UTC day) and this month's requests and tokens. Only API calls count — chat in the app is separate. The same numbers arrive on every completion in the X-RateLimit-* headers.
curl https://www.zerolimitai.com/api/v1/usage \ -H "Authorization: Bearer zlai_xxxxxxxxxxxxxxxxxxxxxxxx"
{
"today": { "used": 42, "limit": 5000, "remaining": 4958, "resets_at": "2026-09-20T00:00:00.000Z" },
"month": { "requests": 1204, "tokens_in": 892400, "tokens_out": 412100 },
"plan": "LIFETIMEPRO"
}Errors use the OpenAI shape, so the official SDKs surface error.message and error.code directly:
{
"error": {
"message": "temperature must be a number between 0 and 2.",
"type": "invalid_request_error",
"code": "invalid_param",
"param": "temperature"
}
}| Status | code | Meaning |
|---|---|---|
400 | invalid_json | Body is not valid JSON. |
400 | missing_messages | messages is missing or empty. |
400 | invalid_param | A sampling field is out of range or the wrong type (temperature, top_p, stop, seed, penalties, max_tokens). The error's param names it. |
401 | invalid_key | Missing or invalid API key. |
403 | upgrade_required | Your plan does not include API access. |
403 | account_suspended | The account behind the key is suspended. |
404 | agent_not_found | agentId unknown or not owned by your account. |
413 | request_too_large | Free accounts only: the request is over about 16,000 input tokens. Send a shorter conversation, or use Annual or Lifetime. |
429 | rate_limited | More than 60 requests/min on this key (or the IP burst limit). Retry-After header in seconds. |
429 | daily_limit | Daily API quota used. X-RateLimit-Reset says when it resets (midnight UTC). |
502 | upstream_error | Every model in the chain failed. Rare: the chain has several free models plus a safety net. Retry. |
type is one of invalid_request_error, authentication_error, permission_error, rate_limit_error, api_error.
Daily quotas are per account (all keys share it) and reset at midnight UTC. Every successful completion returns X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (unix time). A 429 carries Retry-After in seconds. Need more than 5,000/day? Write to us.
Add a Groq and/or OpenRouter key in Integrations and the same endpoint keeps answering past your daily calls: once they run out — or if our quota fails — requests go to your own key, with the same ranking, failover and no-training filter, and no limit of ours. Those responses carry X-ZeroLimit-Keys: own instead of the quota headers, and do not count against the daily calls. We only call free models (Groq's free-plan models and OpenRouter's :free ones); free accounts on both providers work, and an account with a card attached may be billed by its provider. Requests with tools on your own keys need the OpenRouter key. A rejected key returns 502 upstream_error naming it.
Anything that speaks the OpenAI format and lets you set a base URL works unchanged. Change two values and use "auto" as the model.
// Node.js — npm install openai
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://www.zerolimitai.com/api/v1",
apiKey: "zlai_xxxxxxxxxxxxxxxxxxxxxxxx",
});
const response = await client.chat.completions.create({
model: "auto",
messages: [{ role: "user", content: "Hello!" }],
});
console.log(response.choices[0].message.content);# LangChain (Python) — pip install langchain-openai from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="auto", base_url="https://www.zerolimitai.com/api/v1", api_key="zlai_xxxxxxxxxxxxxxxxxxxxxxxx")
Step-by-step guides per tool: /docs/integrations. Editor and tool setups (Cline, Roo Code, Continue, Aider, Open WebUI) are on the developers page. The machine-readable version of this reference is the OpenAPI spec and /llms.txt.
Ready to build?
Free key in a minute, no card. Live service health on /status.