Orob API Documentation
Orob is an AI routing protocol that automatically selects the optimal model for every task. Use our API to access 50+ models through a single endpoint with OpenAI and Anthropic-compatible interfaces.
Getting Started
Base URL
All API requests should be made to https://orob.ai. The API supports both streaming (SSE) and non-streaming responses.
Quick Example
Send your first request using the OpenAI-compatible endpoint:
curl https://orob.ai/v1/chat/completions \
-H "Authorization: Bearer bkb_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"messages": [{"role": "user", "content": "Explain quantum computing in one paragraph."}]
}'import requests
resp = requests.post(
"https://orob.ai/v1/chat/completions",
headers={"Authorization": "Bearer bkb_YOUR_API_KEY"},
json={
"model": "auto",
"messages": [{"role": "user", "content": "Explain quantum computing in one paragraph."}]
}
)
print(resp.json()["choices"][0]["message"]["content"])const resp = await fetch("https://orob.ai/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": "Bearer bkb_YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "auto",
messages: [{ role: "user", content: "Explain quantum computing in one paragraph." }]
})
});
const data = await resp.json();
console.log(data.choices[0].message.content);Authentication
All API requests require an API key. Keys use the format bkb_ followed by 48 hexadecimal characters (e.g., bkb_a1b2c3d4e5f6...).
Creating API Keys
Generate keys in the web application at Settings → Usage & Keys. Each key can be named for easy identification and revoked at any time.
Passing Your Key
Include your key using either method:
- Authorization header (recommended):
Authorization: Bearer bkb_... - X-Api-Key header:
X-Api-Key: bkb_...
# Using Authorization header
curl https://orob.ai/v1/chat/completions \
-H "Authorization: Bearer bkb_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "auto", "messages": [{"role": "user", "content": "Hello"}]}'
# Using X-Api-Key header
curl https://orob.ai/v1/chat/completions \
-H "X-Api-Key: bkb_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "auto", "messages": [{"role": "user", "content": "Hello"}]}'import requests
headers = {"Authorization": "Bearer bkb_YOUR_API_KEY"}
# Or alternatively:
# headers = {"X-Api-Key": "bkb_YOUR_API_KEY"}
resp = requests.post(
"https://orob.ai/v1/chat/completions",
headers=headers,
json={"model": "auto", "messages": [{"role": "user", "content": "Hello"}]}
)
print(resp.json())const headers = {
"Authorization": "Bearer bkb_YOUR_API_KEY",
"Content-Type": "application/json"
};
// Or alternatively:
// headers["X-Api-Key"] = "bkb_YOUR_API_KEY";
const resp = await fetch("https://orob.ai/v1/chat/completions", {
method: "POST",
headers,
body: JSON.stringify({
model: "auto",
messages: [{ role: "user", content: "Hello" }]
})
});
console.log(await resp.json());Chat API (Native)
The native Chat API provides the full Orob experience, including intelligent routing, file generation, tool execution, and decomposed multi-step pipelines via Server-Sent Events.
Request Body
role and content fields.
"auto" for intelligent routing.
meta SSE event.
Message Format
Messages follow the standard chat format. To include file uploads, add a files array:
curl https://orob.ai/api/chat \
-H "Authorization: Bearer bkb_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [{
"role": "user",
"content": "Summarize this document",
"files": [{
"name": "report.pdf",
"type": "application/pdf",
"data_b64": "JVBERi0xLjQK..."
}]
}]
}'import requests
import base64
with open("report.pdf", "rb") as f:
b64 = base64.b64encode(f.read()).decode()
resp = requests.post(
"https://orob.ai/api/chat",
headers={"Authorization": "Bearer bkb_YOUR_API_KEY"},
json={
"messages": [{
"role": "user",
"content": "Summarize this document",
"files": [{"name": "report.pdf", "type": "application/pdf", "data_b64": b64}]
}]
},
stream=True
)
for line in resp.iter_lines():
if line:
print(line.decode())const fileBuffer = await fs.promises.readFile("report.pdf");
const b64 = fileBuffer.toString("base64");
const resp = await fetch("https://orob.ai/api/chat", {
method: "POST",
headers: {
"Authorization": "Bearer bkb_YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
messages: [{
role: "user",
content: "Summarize this document",
files: [{ name: "report.pdf", type: "application/pdf", data_b64: b64 }]
}]
})
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(decoder.decode(value));
}SSE Response
The response is a Server-Sent Events stream. Each event is a JSON object on a data: line. Key event types:
| Event Type | Description |
|---|---|
meta | Routing metadata: model chosen, category, conversationId |
token | Streamed text token: { "type": "token", "text": "..." } |
done | Completion signal with latencyMs, costUsd, inputTokens, outputTokens, model, costBreakdown |
file | Generated file: { "type": "file", "fileUrl": "...", "fileName": "..." } |
image | Generated image: { "type": "image", "imageUrls": ["..."] } |
error | Error: { "type": "error", "error": "..." } |
See the SSE Event Reference for the complete list.
OpenAI Compatibility
Drop-in replacement for the OpenAI API. Point your existing OpenAI SDK at Orob and benefit from intelligent routing without changing your application code.
Request Body
role, content).
"auto" for Orob routing.
Python (OpenAI SDK)
from openai import OpenAI
client = OpenAI(
base_url="https://orob.ai/v1",
api_key="bkb_YOUR_API_KEY"
)
# Non-streaming
response = client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "Write a haiku about APIs"}]
)
print(response.choices[0].message.content)
# Streaming
stream = client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "Write a haiku about APIs"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")curl https://orob.ai/v1/chat/completions \
-H "Authorization: Bearer bkb_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"messages": [{"role": "user", "content": "Write a haiku about APIs"}],
"stream": true
}'const resp = await fetch("https://orob.ai/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": "Bearer bkb_YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "auto",
messages: [{ role: "user", content: "Write a haiku about APIs" }],
stream: true
})
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
for (const line of text.split("\n")) {
if (line.startsWith("data: ") && line !== "data: [DONE]") {
const chunk = JSON.parse(line.slice(6));
const content = chunk.choices?.[0]?.delta?.content;
if (content) process.stdout.write(content);
}
}
}Response Format (Non-streaming)
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1712000000,
"model": "claude-sonnet-4-20250514",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "Requests flowing fast\nEndpoints light the path ahead\nData finds its way"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 14,
"completion_tokens": 20,
"total_tokens": 34
}
}Anthropic Compatibility
Drop-in replacement for the Anthropic Messages API. Use the Anthropic SDK with Orob for automatic model routing.
Request Body
role, content).
"auto" for Orob routing.
from anthropic import Anthropic
client = Anthropic(
base_url="https://orob.ai/v1",
api_key="bkb_YOUR_API_KEY"
)
message = client.messages.create(
model="auto",
max_tokens=1024,
messages=[{"role": "user", "content": "Explain recursion simply."}]
)
print(message.content[0].text)curl https://orob.ai/v1/messages \
-H "Authorization: Bearer bkb_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "auto",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Explain recursion simply."}]
}'const resp = await fetch("https://orob.ai/v1/messages", {
method: "POST",
headers: {
"Authorization": "Bearer bkb_YOUR_API_KEY",
"Content-Type": "application/json",
"anthropic-version": "2023-06-01"
},
body: JSON.stringify({
model: "auto",
max_tokens: 1024,
messages: [{ role: "user", content: "Explain recursion simply." }]
})
});
const data = await resp.json();
console.log(data.content[0].text);Models & Routing
Orob uses a multi-armed bandit algorithm to learn the best model for each task category over time. You can let the router choose automatically, or specify a model directly.
Returns all available models with their capabilities, pricing, and category support.
OpenAI-compatible model list. Returns models in the standard { "data": [...] } format.
Classify a prompt and get the recommended model without generating a response.
curl https://orob.ai/api/route \
-H "Authorization: Bearer bkb_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Write a Python script that scrapes news headlines"}'import requests
resp = requests.post(
"https://orob.ai/api/route",
headers={"Authorization": "Bearer bkb_YOUR_API_KEY"},
json={"prompt": "Write a Python script that scrapes news headlines"}
)
data = resp.json()
print(f"Category: {data['category']}")
print(f"Subcategory: {data['subcategory']}")
print(f"Recommended model: {data['model']}")const resp = await fetch("https://orob.ai/api/route", {
method: "POST",
headers: {
"Authorization": "Bearer bkb_YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
prompt: "Write a Python script that scrapes news headlines"
})
});
const data = await resp.json();
console.log(`Category: ${data.category}`);
console.log(`Recommended model: ${data.model}`);How Routing Works
When you send "model": "auto", Orob classifies your prompt into a task category, then selects the best-performing model for that category using Thompson Sampling (a multi-armed bandit strategy). The router balances exploration of new models with exploitation of known winners.
Model Categories
| Category | Description | Example Models |
|---|---|---|
| Text | General conversation, analysis, summarization, creative writing | GPT-4o, Claude Sonnet, Gemini Pro |
| Code | Code generation, debugging, code review | Claude Sonnet, GPT-4o, DeepSeek Coder |
| Image Generation | Create images from text descriptions | FLUX, Stable Diffusion, DALL-E |
| Video Generation | Create videos from text or images | Kling, Minimax, Runway |
| Document Generation | Create PDFs, DOCX, PPTX, XLSX files | Routed through text models + tools |
Pricing
Returns per-model token pricing with the 2.5% platform overhead included.
Credit System
Orob uses a credit-based system where 1 credit = $1 of API cost. Credits are deducted based on actual token usage at each model's rate, plus a 2.5% platform overhead applied transparently.
curl https://orob.ai/api/pricing \ -H "Authorization: Bearer bkb_YOUR_API_KEY"
import requests
resp = requests.get(
"https://orob.ai/api/pricing",
headers={"Authorization": "Bearer bkb_YOUR_API_KEY"}
)
for model in resp.json()["models"]:
print(f"{model['name']}: ${model['inputPer1M']}/1M input, ${model['outputPer1M']}/1M output")const resp = await fetch("https://orob.ai/api/pricing", {
headers: { "Authorization": "Bearer bkb_YOUR_API_KEY" }
});
const { models } = await resp.json();
models.forEach(m =>
console.log(`${m.name}: $${m.inputPer1M}/1M input, $${m.outputPer1M}/1M output`)
);Example Pricing (per 1M tokens, including 2.5% overhead)
| Model | Input | Output |
|---|---|---|
| GPT-4o | $2.56 | $10.25 |
| GPT-4o Mini | $0.15 | $0.62 |
| Claude Sonnet 4 | $3.08 | $15.38 |
| Claude Haiku 3.5 | $0.82 | $4.10 |
| Gemini 2.0 Flash | $0.10 | $0.41 |
| DeepSeek V3 | $0.28 | $1.13 |
/api/pricing for the current rates, as model providers may update their pricing.
Files & Media
Upload files for processing and download generated outputs. Orob supports reading, transforming, and generating a wide range of file types.
Uploading Files
Include files in your chat messages using base64 encoding:
import requests, base64
with open("data.xlsx", "rb") as f:
b64 = base64.b64encode(f.read()).decode()
resp = requests.post(
"https://orob.ai/api/chat",
headers={"Authorization": "Bearer bkb_YOUR_API_KEY"},
json={
"messages": [{
"role": "user",
"content": "Analyze this spreadsheet and create a summary PDF",
"files": [{"name": "data.xlsx", "type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "data_b64": b64}]
}]
},
stream=True
)# Base64 encode your file first
B64=$(base64 -i data.xlsx)
curl https://orob.ai/api/chat \
-H "Authorization: Bearer bkb_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"messages\": [{
\"role\": \"user\",
\"content\": \"Analyze this spreadsheet\",
\"files\": [{\"name\": \"data.xlsx\", \"type\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\", \"data_b64\": \"$B64\"}]
}]
}"const fs = require("fs");
const b64 = fs.readFileSync("data.xlsx").toString("base64");
const resp = await fetch("https://orob.ai/api/chat", {
method: "POST",
headers: {
"Authorization": "Bearer bkb_YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
messages: [{
role: "user",
content: "Analyze this spreadsheet",
files: [{ name: "data.xlsx", type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", data_b64: b64 }]
}]
})
});Downloading Generated Files
Download a generated file by its ID. The file URL is returned in SSE file, image, video, or audio events.
Supported File Types
| Type | Extensions | Notes |
|---|---|---|
| Documents | PDF, DOCX, PPTX, XLSX | Read, generate, and transform |
| Images | PNG, JPG, SVG | Read, generate, and edit |
| Audio | MP3, WAV | Transcription and text-to-speech |
| Video | MP4 | Generation and editing |
| Data | CSV, JSON, TXT | Read and generate |
Usage & Billing
Returns your current credit balance and rate limit status.
Per-model cost breakdown for the current billing period.
Paginated transaction history. Supports ?page=1&limit=50 query parameters.
Redeem a promotional code to add credits to your account.
# Check credit balance
curl https://orob.ai/api/usage/summary \
-H "Authorization: Bearer bkb_YOUR_API_KEY"
# Get per-model breakdown
curl https://orob.ai/api/usage/by-model \
-H "Authorization: Bearer bkb_YOUR_API_KEY"
# Redeem a promo code
curl https://orob.ai/api/redeem \
-H "Authorization: Bearer bkb_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"code": "WELCOME50"}'import requests
headers = {"Authorization": "Bearer bkb_YOUR_API_KEY"}
# Check credit balance
summary = requests.get("https://orob.ai/api/usage/summary", headers=headers).json()
print(f"Credits remaining: {summary['credits']}")
# Per-model breakdown
by_model = requests.get("https://orob.ai/api/usage/by-model", headers=headers).json()
for entry in by_model["models"]:
print(f" {entry['model']}: ${entry['cost']:.4f}")
# Redeem promo code
result = requests.post(
"https://orob.ai/api/redeem",
headers=headers,
json={"code": "WELCOME50"}
)
print(result.json())const headers = { "Authorization": "Bearer bkb_YOUR_API_KEY" };
// Check credit balance
const summary = await fetch("https://orob.ai/api/usage/summary", { headers });
const { credits } = await summary.json();
console.log(`Credits remaining: ${credits}`);
// Per-model breakdown
const byModel = await fetch("https://orob.ai/api/usage/by-model", { headers });
const { models } = await byModel.json();
models.forEach(m => console.log(` ${m.model}: $${m.cost.toFixed(4)}`));
// Redeem promo code
const result = await fetch("https://orob.ai/api/redeem", {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ code: "WELCOME50" })
});
console.log(await result.json());SSE Event Reference
Complete reference for all Server-Sent Event types returned by the /api/chat endpoint. Each event is a JSON object on a data: line.
| Type | Key Fields | Description |
|---|---|---|
meta |
model, category, subcategory, conversationId |
Sent first. Contains routing decision and session metadata. |
token |
text |
Streamed text token. Concatenate to build the full response. |
done |
latencyMs, costUsd, inputTokens, outputTokens, model, costBreakdown |
Completion signal with final metrics. |
error |
error |
Error message. Stream ends after this event. |
file |
fileUrl, fileName, model |
Generated document (PDF, DOCX, PPTX, XLSX, etc.). |
image |
imageUrls, model, revised_prompt |
Generated image. |
video |
videoUrl, model |
Generated video. |
audio |
fileUrl, fileName, model |
Generated audio (text-to-speech). |
pipeline |
steps, currentStep |
Pipeline progress for decomposed multi-step tasks. |
decomposition |
subtasks |
Task decomposition plan showing all subtasks. |
quality |
score, taskType, breakdown |
Quality evaluation result for the response. |
strategy_decision |
strategy, reason |
Whether task was handled directly or decomposed, and why. |
tool_execution |
tool, status |
Tool execution started (e.g., code sandbox, web search). |
tool_result |
tool, result |
Tool execution completed with result. |
slide_progress |
current, total |
Slide generation progress for presentations. |
svg |
svg |
Inline SVG content for diagram rendering. |
Example: Parsing SSE Stream
import requests, json
resp = requests.post(
"https://orob.ai/api/chat",
headers={"Authorization": "Bearer bkb_YOUR_API_KEY"},
json={"messages": [{"role": "user", "content": "Create a PDF report about AI trends"}]},
stream=True
)
for line in resp.iter_lines():
line = line.decode()
if not line.startswith("data: "):
continue
event = json.loads(line[6:])
if event["type"] == "meta":
print(f"Routed to: {event['model']}")
elif event["type"] == "token":
print(event["text"], end="", flush=True)
elif event["type"] == "file":
print(f"\nFile generated: {event['fileName']}")
print(f"Download: https://orob.ai{event['fileUrl']}")
elif event["type"] == "done":
print(f"\nCost: ${event['costUsd']:.4f} | Latency: {event['latencyMs']}ms")const resp = await fetch("https://orob.ai/api/chat", {
method: "POST",
headers: {
"Authorization": "Bearer bkb_YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
messages: [{ role: "user", content: "Create a PDF report about AI trends" }]
})
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const event = JSON.parse(line.slice(6));
switch (event.type) {
case "meta": console.log(`Routed to: ${event.model}`); break;
case "token": process.stdout.write(event.text); break;
case "file": console.log(`\nFile: ${event.fileName} -> ${event.fileUrl}`); break;
case "done": console.log(`\nCost: $${event.costUsd} | ${event.latencyMs}ms`); break;
case "error": console.error(`Error: ${event.error}`); break;
}
}
}Rate Limits & Errors
Rate Limits
The default rate limit is 60 requests per minute per API key. Rate limit headers are included in every response:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
Error Format
REST endpoints return JSON errors. SSE endpoints emit an error event.
// HTTP 401 Unauthorized
{
"error": "Invalid or missing API key"
}
// HTTP 402 Payment Required
{
"error": "Insufficient credits"
}
// HTTP 429 Too Many Requests
{
"error": "Rate limit exceeded. Retry after 12 seconds."
}data: {"type": "error", "error": "Model unavailable, retrying with fallback..."}
data: {"type": "error", "error": "Insufficient credits to complete this request"}HTTP Status Codes
| Code | Meaning | Action |
|---|---|---|
| 400 | Bad Request | Check request body format and required fields. |
| 401 | Unauthorized | Check your API key is valid and correctly formatted. |
| 402 | Payment Required | Add credits to your account. |
| 403 | Forbidden | Your key does not have permission for this resource. |
| 429 | Too Many Requests | Back off and retry. See rate limit headers. |
| 500 | Internal Server Error | Retry with exponential backoff. Contact support if persistent. |
Retry Strategy
Use exponential backoff with jitter for retries:
import time, random, requests
def call_with_retry(url, headers, json, max_retries=3):
for attempt in range(max_retries):
resp = requests.post(url, headers=headers, json=json)
if resp.status_code == 429:
wait = (2 ** attempt) + random.random()
time.sleep(wait)
continue
resp.raise_for_status()
return resp.json()
raise Exception("Max retries exceeded")async function callWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const resp = await fetch(url, options);
if (resp.status === 429) {
const wait = Math.pow(2, attempt) + Math.random();
await new Promise(r => setTimeout(r, wait * 1000));
continue;
}
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.json();
}
throw new Error("Max retries exceeded");
}API Keys
Programmatically manage your API keys. You must be authenticated (via session or existing API key) to use these endpoints.
Generate a new API key.
List all API keys for your account. Keys are returned with the prefix visible and the rest masked.
Permanently revoke an API key. This action cannot be undone.
# Create a new key
curl -X POST https://orob.ai/api/keys \
-H "Authorization: Bearer bkb_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Production"}'
# List all keys
curl https://orob.ai/api/keys \
-H "Authorization: Bearer bkb_YOUR_API_KEY"
# Revoke a key
curl -X DELETE https://orob.ai/api/keys/KEY_ID \
-H "Authorization: Bearer bkb_YOUR_API_KEY"import requests
headers = {"Authorization": "Bearer bkb_YOUR_API_KEY"}
# Create a new key
new_key = requests.post(
"https://orob.ai/api/keys",
headers=headers,
json={"name": "Production"}
).json()
print(f"New key: {new_key['key']}") # Full key shown only at creation
# List all keys
keys = requests.get("https://orob.ai/api/keys", headers=headers).json()
for k in keys["keys"]:
print(f" {k['name']}: {k['prefix']}... (created {k['created_at']})")
# Revoke a key
requests.delete(f"https://orob.ai/api/keys/{key_id}", headers=headers)const headers = {
"Authorization": "Bearer bkb_YOUR_API_KEY",
"Content-Type": "application/json"
};
// Create a new key
const newKey = await fetch("https://orob.ai/api/keys", {
method: "POST", headers,
body: JSON.stringify({ name: "Production" })
}).then(r => r.json());
console.log(`New key: ${newKey.key}`);
// List all keys
const { keys } = await fetch("https://orob.ai/api/keys", { headers }).then(r => r.json());
keys.forEach(k => console.log(` ${k.name}: ${k.prefix}...`));
// Revoke a key
await fetch(`https://orob.ai/api/keys/${keyId}`, {
method: "DELETE", headers
});