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

https://orob.ai

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:

# 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());
Security. Never expose your API key in client-side code or public repositories. Use environment variables or a backend proxy for production applications.

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.

POST /api/chat

Request Body

messages array required Array of message objects with role and content fields.
model string optional Model ID to use. Omit or set to "auto" for intelligent routing.
conversationId string optional Conversation ID for multi-turn context. Returned in the 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 TypeDescription
metaRouting metadata: model chosen, category, conversationId
tokenStreamed text token: { "type": "token", "text": "..." }
doneCompletion signal with latencyMs, costUsd, inputTokens, outputTokens, model, costBreakdown
fileGenerated file: { "type": "file", "fileUrl": "...", "fileName": "..." }
imageGenerated image: { "type": "image", "imageUrls": ["..."] }
errorError: { "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.

POST /v1/chat/completions

Request Body

messages array required Array of message objects (role, content).
model string required Model ID or "auto" for Orob routing.
temperature number optional Sampling temperature (0–2). Default: 0.2.
max_tokens integer optional Maximum tokens to generate. Default: 2048.
stream boolean optional Enable SSE streaming. Default: true.

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.

POST /v1/messages

Request Body

messages array required Array of message objects (role, content).
model string required Model ID or "auto" for Orob routing.
system string optional System prompt.
max_tokens integer required Maximum tokens to generate.
stream boolean optional Enable SSE streaming. Default: false.
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.

GET /api/models

Returns all available models with their capabilities, pricing, and category support.

GET /v1/models

OpenAI-compatible model list. Returns models in the standard { "data": [...] } format.

POST /api/route

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

CategoryDescriptionExample Models
TextGeneral conversation, analysis, summarization, creative writingGPT-4o, Claude Sonnet, Gemini Pro
CodeCode generation, debugging, code reviewClaude Sonnet, GPT-4o, DeepSeek Coder
Image GenerationCreate images from text descriptionsFLUX, Stable Diffusion, DALL-E
Video GenerationCreate videos from text or imagesKling, Minimax, Runway
Document GenerationCreate PDFs, DOCX, PPTX, XLSX filesRouted through text models + tools

Pricing

GET /api/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)

ModelInputOutput
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
Note. Prices shown include the 2.5% platform overhead. Check /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

GET /api/files/:id/download

Download a generated file by its ID. The file URL is returned in SSE file, image, video, or audio events.

Supported File Types

TypeExtensionsNotes
DocumentsPDF, DOCX, PPTX, XLSXRead, generate, and transform
ImagesPNG, JPG, SVGRead, generate, and edit
AudioMP3, WAVTranscription and text-to-speech
VideoMP4Generation and editing
DataCSV, JSON, TXTRead and generate

Usage & Billing

GET /api/usage/summary

Returns your current credit balance and rate limit status.

GET /api/usage/by-model

Per-model cost breakdown for the current billing period.

GET /api/usage/history

Paginated transaction history. Supports ?page=1&limit=50 query parameters.

POST /api/redeem

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.

TypeKey FieldsDescription
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:

HeaderDescription
X-RateLimit-LimitMaximum requests per minute
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix 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

CodeMeaningAction
400Bad RequestCheck request body format and required fields.
401UnauthorizedCheck your API key is valid and correctly formatted.
402Payment RequiredAdd credits to your account.
403ForbiddenYour key does not have permission for this resource.
429Too Many RequestsBack off and retry. See rate limit headers.
500Internal Server ErrorRetry 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.

POST /api/keys

Generate a new API key.

name string optional Human-readable name for the key (e.g., "Production", "CI/CD").
GET /api/keys

List all API keys for your account. Keys are returned with the prefix visible and the rest masked.

DELETE /api/keys/:id

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
});
Important. The full API key is only shown once at creation. Store it securely. If you lose a key, revoke it and create a new one.