API Documentation
Complete reference for the RooyaLLM API. OpenAI-compatible interface that works with every major AI provider.
https://llm.rooyai.comOverview
RooyaLLM provides a unified, OpenAI-compatible API that routes requests to 100+ AI models through a single endpoint. One API key, one base URL — access every model we support.
| Feature | Description |
|---|---|
| Unified API | Single endpoint for every LLM - OpenAI-compatible interface |
| Streaming | Full SSE streaming support with real-time token delivery |
| ASR / Speech-to-Text | OpenAI-compatible /v1/audio/transcriptions for speech-to-text transcription (up to 25 MB) |
| TTS / Text-to-Speech | OpenAI-compatible /v1/audio/speech — converts text to audio. Streamed, zero-copy delivery. Billed per input character. |
| OCR / Document Extraction | Mistral-powered /v1/ocr — extract structured text from PDFs and images (up to 50 MB, billed per page) |
| Image Generation | FLUX.1 & turbo image models via /v1/images/generations — OpenAI-compatible with gateway-side format conversion (PNG/JPEG/WebP) and compression |
| Cost Tracking | Automatic per-request cost calculation and budget enforcement |
| Rate Limiting | Per-key RPM + TPD limits with Redis-backed sliding windows |
| 3-Tier Auth Cache | In-memory -> Redis -> Database key validation (<1us hot path) |
| Model Health | Auto-synced model catalog with live health monitoring |
Authentication
All API requests (except /v1/models and /health) require a valid API key via the Authorization header using the Bearer scheme.
Authorization: Bearer sk-your-api-keyObtaining an API Key
- Sign up at the registration page
- Navigate to Dashboard → API Keys
- Click Create Key, set a name and optional budget limit
- Copy the key immediately — it is shown only once
Key Format
sk-<random-alphanumeric-string>Quick Start
The fastest way to get started — just point your OpenAI SDK to your gateway URL and set your API key:
from openai import OpenAI
client = OpenAI(
base_url="https://llm.rooyai.com/v1",
api_key="sk-your-api-key"
)
response = client.chat.completions.create(
model="<your-chat-model>",
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)That's it. Every OpenAI SDK feature works — streaming, function calling, vision, and more.
Chat Completions
Creates a chat completion for a conversation. This is the primary endpoint for interacting with LLMs.
/v1/chat/completionsRequest Body
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | ✓ | - | Model ID. Use GET /v1/models to see all available models. |
messages | array | ✓ | - | Array of message objects with role and content |
temperature | number | — | 1.0 | Sampling temperature between 0 and 2 |
top_p | number | — | 1.0 | Nucleus sampling between 0 and 1 |
n | integer | — | 1 | Number of completions to generate (1-128) |
stream | boolean | — | false | Enable Server-Sent Events streaming |
stop | string | array | — | null | Up to 4 stop sequences |
max_tokens | integer | — | - | Maximum tokens to generate |
presence_penalty | number | — | 0 | Penalty for new topics (-2 to 2) |
frequency_penalty | number | — | 0 | Penalty for repetition (-2 to 2) |
user | string | — | - | Unique end-user identifier |
Example Request
curl https://llm.rooyai.com/v1/chat/completions \\
-H "Authorization: Bearer sk-your-api-key" \\
-H "Content-Type: application/json" \\
-d '{
"model": "<your-chat-model>",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain quantum computing in simple terms."}
],
"temperature": 0.7,
"max_tokens": 500
}'{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1714000000,
"model": "<model-id>",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Quantum computing uses qubits instead of traditional bits..."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 28,
"completion_tokens": 150,
"total_tokens": 178
}
}Completions (Legacy)
Creates a text completion. This is the legacy completions endpoint for non-chat models.
/v1/completions| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | ✓ | - | Model ID |
prompt | string | array | ✓ | - | The prompt(s) to complete |
suffix | string | — | null | Text after the completion |
max_tokens | integer | — | 16 | Maximum tokens to generate |
temperature | number | — | 1.0 | Sampling temperature (0-2) |
top_p | number | — | 1.0 | Nucleus sampling (0-1) |
n | integer | — | 1 | Number of completions (1-128) |
stream | boolean | — | false | Enable SSE streaming |
stop | string | array | — | null | Up to 4 stop sequences |
curl https://llm.rooyai.com/v1/completions \\
-H "Authorization: Bearer sk-your-api-key" \\
-H "Content-Type: application/json" \\
-d '{
"model": "<your-completion-model>",
"prompt": "Write a haiku about programming:",
"max_tokens": 50,
"temperature": 0.8
}'Embeddings
Generate vector embeddings for text input.
/v1/embeddings| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | ✓ | - | Embedding model ID. Use GET /v1/models to see available embedding models. |
input | string | array | ✓ | - | Text(s) to embed |
encoding_format | string | — | float | float or base64 |
user | string | — | - | End-user identifier |
curl https://llm.rooyai.com/v1/embeddings \\
-H "Authorization: Bearer sk-your-api-key" \\
-H "Content-Type: application/json" \\
-d '{
"model": "<your-embedding-model>",
"input": "The quick brown fox jumps over the lazy dog"
}'{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [0.0023, -0.0091, 0.0152, ...]
}
],
"model": "<embedding-model-id>",
"usage": { "prompt_tokens": 9, "total_tokens": 9 }
}List Models
Returns a list of all available models. No authentication required.
/v1/models{
"object": "list",
"data": [
{ "id": "<model-id-1>", "object": "model", "created": 1714000000, "owned_by": "your-gateway" },
{ "id": "<model-id-2>", "object": "model", "created": 1714000000, "owned_by": "your-gateway" }
]
}Get Model
Retrieve details for a specific model. No authentication required.
/v1/models/{model_id}| Parameter | Description |
|---|---|
model_id | The model ID to retrieve. Use GET /v1/models to list all available IDs. |
{
"id": "<model-id>",
"object": "model",
"created": 1714000000,
"owned_by": "your-gateway"
}Model Health Status
Returns the full model catalog with live health status, availability, and latency data. No authentication required.
/v1/models/status{
"object": "list",
"data": [
{
"id": "uuid",
"model_id": "<model-id>",
"model_type": "chat",
"is_available": true,
"health_status": "healthy",
"last_health_latency_ms": 245
}
],
"total": 150, "healthy": 142, "down": 3, "unknown": 5
}Audio Transcriptions (ASR)
NEWTranscribes an audio file into text using speech recognition models. Fully OpenAI-compatible — just change base_url and api_key in your existing transcription code.
/v1/audio/transcriptionsmultipart/form-dataAuth requiredRequest Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file | file (binary) | ✓ | — | Audio file to transcribe. Maximum size: 25 MB. |
model | string | ✓ | — | Audio transcription model ID. Use GET /v1/models to list available speech-to-text models. |
language | string | — | auto-detect | ISO 639-1 language code (e.g. en, ar, fr). Improves speed & accuracy when set. |
response_format | string | — | verbose_json | Output format: verbose_json | json | text | srt | vtt. |
temperature | number | — | 0 | Sampling temperature 0–1. 0 = deterministic greedy decoding. |
prompt | string | — | — | Optional hint to guide the model style or continue a prior transcript. |
Supported Audio Formats
413 Request Entity Too Large.| Format | MIME Types |
|---|---|
MP3 | audio/mpeg, audio/mp3 |
MP4 / M4A | audio/mp4, audio/m4a, audio/x-m4a |
WAV | audio/wav, audio/wave |
OGG | audio/ogg |
FLAC | audio/flac |
WebM | audio/webm |
AAC | audio/aac |
Opus | audio/opus |
Response Formats
| response_format | Content-Type | Description |
|---|---|---|
verbose_json | application/json | (Default) Full JSON with text, duration, language, and word-level segments. Recommended — used for accurate billing. |
json | application/json | Compact JSON with just the text field. |
text | text/plain | Raw transcription text — no JSON wrapper. |
srt | text/plain | SubRip subtitle format with timestamps. |
vtt | text/plain | WebVTT subtitle format. |
Live Endpoint Tester
Example Requests (cURL)
# verbose_json (recommended — includes duration for accurate billing)
curl https://llm.rooyai.com/v1/audio/transcriptions \
-H "Authorization: Bearer sk-your-api-key" \
-F "file=@/path/to/audio.mp3" \
-F "model=whisper-large-v3-turbo" \
-F "language=en" \
-F "response_format=verbose_json"
# Plain text output
curl https://llm.rooyai.com/v1/audio/transcriptions \
-H "Authorization: Bearer sk-your-api-key" \
-F "file=@/path/to/meeting.m4a" \
-F "model=whisper-large-v3-turbo" \
-F "response_format=text"
# SRT subtitle format
curl https://llm.rooyai.com/v1/audio/transcriptions \
-H "Authorization: Bearer sk-your-api-key" \
-F "file=@/path/to/video.webm" \
-F "model=whisper-large-v3-turbo" \
-F "response_format=srt"Example Responses
{
"task": "transcribe",
"language": "english",
"duration": 42.5,
"text": "Welcome to the API. This is a transcription test.",
"segments": [
{
"id": 0,
"start": 0.0,
"end": 4.2,
"text": " Welcome to the API.",
"avg_logprob": -0.21,
"no_speech_prob": 0.003
},
{
"id": 1,
"start": 4.2,
"end": 8.1,
"text": " This is a transcription test.",
"avg_logprob": -0.18,
"no_speech_prob": 0.001
}
]
}Billing
ASR requests are billed by audio duration (seconds), not tokens. The duration field in the verbose_json response is used directly:
cost = duration_seconds × (input_price_per_1M ÷ 1,000,000)response_format=verbose_json (the default) to get the duration field for most accurate billing. For text, srt, and vtt formats, billing falls back to an estimate derived from output character count.SDK Examples
from openai import OpenAI
client = OpenAI(
base_url="https://llm.rooyai.com/v1",
api_key="sk-your-api-key"
)
# verbose_json — includes duration for accurate billing
with open("/path/to/audio.mp3", "rb") as audio_file:
transcript = client.audio.transcriptions.create(
model="whisper-large-v3-turbo",
file=audio_file,
language="en", # optional — improves speed & accuracy
response_format="verbose_json", # recommended
)
print(transcript.text)
print(f"Language : {transcript.language}")
print(f"Duration : {transcript.duration:.1f}s")
for seg in transcript.segments:
print(f" [{seg.start:.1f}s -> {seg.end:.1f}s] {seg.text}")
# Plain text (no duration field returned)
with open("/path/to/audio.mp3", "rb") as audio_file:
text = client.audio.transcriptions.create(
model="whisper-large-v3-turbo",
file=audio_file,
response_format="text",
)
print(text)Error Responses
| Status | Reason |
|---|---|
400 | Missing file or model field, or malformed multipart/form-data body |
401 | Invalid or missing API key |
402 | Insufficient credits or budget limit exceeded |
413 | Audio file exceeds the 25 MB limit |
429 | Rate limit exceeded |
504 | Upstream transcription timed out (audio processing took > 300s) |
Text to Speech (TTS)
NEWConverts text into spoken audio using AI voice synthesis models. Fully OpenAI-compatible — drop in your existing client.audio.speech.create() calls by changing base_url and api_key. Audio is streamed zero-copy from the upstream to your client — no intermediate buffering.
/v1/audio/speechapplication/jsonAuth requiredStreaming responseRequest Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | ✓ | — | TTS model ID. Use GET /v1/models to list available speech synthesis models. |
input | string | ✓ | — | Text to synthesize. Maximum 4,096 characters. |
voice | string | ✓ | — | Voice ID to use for synthesis. Standard voices: alloy, echo, fable, onyx, nova, shimmer. Custom providers may support additional voice IDs. |
response_format | string | — | mp3 | Audio output format: mp3 | opus | aac | flac | wav | pcm. |
speed | number | — | 1.0 | Playback speed multiplier. Range: 0.25 – 4.0. Values outside this range are clamped. |
Standard Voices
The following voices are available across all models that support the OpenAI TTS API. Custom providers may expose additional voice IDs — pass any valid voice string.
| voice | Characteristics |
|---|---|
alloy | Neutral, balanced — general-purpose. |
echo | Clear, articulate — great for narration. |
fable | Expressive, warm — storytelling. |
onyx | Deep, authoritative — professional announcements. |
nova | Friendly, upbeat — conversational assistants. |
shimmer | Soft, gentle — meditative or calm content. |
Audio Output Formats
| response_format | Content-Type | Notes |
|---|---|---|
mp3 | audio/mpeg | (Default) Widely compatible. Best for web playback and streaming. |
opus | audio/opus | Low latency, high quality. Ideal for real-time or voice chat apps. |
aac | audio/aac | Good quality at low bitrates. Preferred on iOS / Apple platforms. |
flac | audio/flac | Lossless compression. Largest files — use when audio fidelity is critical. |
wav | audio/wav | Uncompressed PCM. Maximum compatibility — large files. |
pcm | audio/pcm | Raw 16-bit PCM samples at 24 kHz. For direct DSP pipelines or custom audio players. |
Live Endpoint Tester
Example Requests (cURL)
# ── 1. Basic MP3 (default format) — save to file ─────────────────────────────
curl https://llm.rooyai.com/v1/audio/speech \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "rooyai-tts",
"input": "Hello! This is a test of the RooyaLLM text-to-speech API.",
"voice": "alloy"
}' \
--output speech.mp3
# ── 2. Specify voice and format ────────────────────────────────────────────
curl https://llm.rooyai.com/v1/audio/speech \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "rooyai-tts",
"input": "مرحباً! هذا اختبار لتقنية تحويل النص إلى كلام.",
"voice": "onyx",
"response_format": "opus",
"speed": 1.0
}' \
--output speech.opus
# ── 3. WAV at slower speed ─────────────────────────────────────────────────
curl https://llm.rooyai.com/v1/audio/speech \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "rooyai-tts",
"input": "Slow and clear narration for accessibility.",
"voice": "nova",
"response_format": "wav",
"speed": 0.75
}' \
--output speech.wavSDK Examples
from openai import OpenAI
from pathlib import Path
client = OpenAI(
base_url="https://llm.rooyai.com/v1",
api_key="sk-your-api-key"
)
# ── Option A: Save directly to a file (OpenAI SDK streaming helper) ───────
with client.audio.speech.with_streaming_response.create(
model="rooyai-tts",
voice="alloy",
input="Hello! This is a test of the RooyaLLM text-to-speech API.",
response_format="mp3", # mp3 | opus | aac | flac | wav | pcm
speed=1.0, # 0.25 – 4.0
) as response:
response.stream_to_file(Path("speech.mp3"))
print("Saved speech.mp3")
# ── Option B: Read into memory (small inputs only) ────────────────────────
audio = client.audio.speech.create(
model="rooyai-tts",
voice="nova",
input="Short text for in-memory synthesis.",
response_format="mp3",
)
audio_bytes = audio.read()
print(f"Received {len(audio_bytes):,} bytes of MP3 audio")
# ── Option C: Stream using requests (for custom handling) ─────────────────
import requests
with requests.post(
f"https://llm.rooyai.com/v1/audio/speech",
headers={
"Authorization": "Bearer sk-your-api-key",
"Content-Type": "application/json",
},
json={
"model": "rooyai-tts",
"input": "Streaming audio delivery with zero buffering.",
"voice": "shimmer",
"response_format": "opus",
"speed": 1.1,
},
stream=True,
timeout=90,
) as r:
r.raise_for_status()
with open("speech.opus", "wb") as f:
for chunk in r.iter_content(chunk_size=4096):
f.write(chunk)
print("Saved speech.opus")Billing
TTS requests are billed by input character count, not tokens or audio duration. Billing is committed before the audio stream is piped to you — so even if you disconnect mid-stream, the synthesis cost is recorded.
cost = char_count × (input_price_per_1M ÷ 1,000,000)429 Too Many Requests. Each request holds one socket open for the duration of the stream — use connection pooling in your client for high-throughput workloads.Error Responses
| Status | Reason |
|---|---|
400 | Missing required field (model, input, or voice), input exceeds 4,096 chars, invalid response_format, or non-numeric speed |
401 | Invalid or missing API key |
402 | Insufficient credits or per-key budget limit exceeded |
429 | Too many concurrent TTS requests — the gateway allows up to 200 simultaneous streams |
502 | Could not reach the TTS upstream — upstream connection refused or reset |
504 | TTS upstream timed out — no response headers received within 90 seconds |
OCR — Document Text Extraction
NEWExtract structured text from PDFs and images using Mistral OCR. Supports URL-based documents, inline base64 data URIs, and direct file uploads up to 50 MB. Results are returned as Markdown — tables, headings, and lists are preserved.
/v1/ocrapplication/jsonAuth required/v1/ocr/uploadmultipart/form-dataAuth requiredusage_info.pages_processed field in every response shows exactly how many pages were charged.Request Parameters — /v1/ocr
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | ✓ | mistral-ocr-latest | OCR model ID. Currently only mistral-ocr-latest is supported. |
document | object | ✓ | — | Document to process. See Document Object below. |
pages | array | string | — | all | Page selection (0-indexed). Array [0,1,4] or range string "0-5,8,10-12". Omit to process all pages. |
include_image_base64 | boolean | — | false | When true, each page object includes a base64-encoded image of the rendered page. |
image_limit | integer | — | — | Maximum number of images to extract per page. |
image_min_size | integer | — | — | Minimum pixel dimension for an image to be extracted. |
include_blocks | boolean | — | false | Include raw layout blocks (bounding boxes, type) in the response. |
extract_header | boolean | — | false | Extract page header text as a separate field. |
extract_footer | boolean | — | false | Extract page footer text as a separate field. |
table_format | string | — | — | Format for extracted tables: "markdown" | "html". |
confidence_scores_granularity | string | — | — | Include OCR confidence scores: "word" | "page". |
Document Object Types
The document field must include a type and the corresponding URL or ID field:
| type | Field | Description |
|---|---|---|
document_url | document_url | URL pointing to a publicly accessible PDF. Also accepts a base64 data URI: data:application/pdf;base64,… |
image_url | image_url | URL pointing to an image (JPEG, PNG, WebP, TIFF, BMP, GIF). Also accepts a base64 data URI: data:image/jpeg;base64,… |
file | file_id | Pre-uploaded Mistral file ID obtained from the Mistral Files API (/v1/files). |
Request Parameters — /v1/ocr/upload
All parameters are sent as multipart/form-data fields (strings, not JSON).
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file | file (binary) | ✓ | — | PDF or image file to OCR. Maximum size: 50 MB. |
model | string | — | mistral-ocr-latest | OCR model ID. |
pages | string | — | all | Page range string, e.g. "0-5,8". |
include_image_base64 | string | — | false | "true" | "false" — include rendered page images in response. |
image_limit | string | — | — | Integer string — max images per page. |
image_min_size | string | — | — | Integer string — min pixel dimension to extract an image. |
include_blocks | string | — | false | "true" | "false" — include layout blocks in response. |
extract_header | string | — | false | "true" | "false" — extract page headers. |
extract_footer | string | — | false | "true" | "false" — extract page footers. |
table_format | string | — | — | "markdown" | "html". |
confidence_scores_granularity | string | — | — | "word" | "page". |
Supported File Formats
413 Request Entity Too Large.| Format | MIME Types | Notes |
|---|---|---|
| application/pdf | All standard PDFs — scanned or native text | |
| JPEG | image/jpeg, image/jpg | Photographs, scanned documents |
| PNG | image/png | Screenshots, diagrams |
| WebP | image/webp | Modern image format |
| TIFF | image/tiff, image/tif | High-quality scans |
| BMP | image/bmp | Bitmap images |
| GIF | image/gif | Static GIF images (first frame only) |
Live Endpoint Tester
Example Requests — URL (cURL)
# OCR a PDF from a public URL
curl https://llm.rooyai.com/v1/ocr \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "mistral-ocr-latest",
"document": {
"type": "document_url",
"document_url": "https://example.com/document.pdf"
}
}'
# OCR an image from a URL
curl https://llm.rooyai.com/v1/ocr \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "mistral-ocr-latest",
"document": {
"type": "image_url",
"image_url": "https://example.com/invoice.jpg"
}
}'
# Process only pages 0-2 and extract tables as Markdown
curl https://llm.rooyai.com/v1/ocr \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "mistral-ocr-latest",
"document": {
"type": "document_url",
"document_url": "https://example.com/report.pdf"
},
"pages": [0, 1, 2],
"table_format": "markdown"
}'Example Requests — File Upload (cURL)
# Upload a local PDF and OCR it in one request
curl https://llm.rooyai.com/v1/ocr/upload \
-H "Authorization: Bearer sk-your-api-key" \
-F "file=@/path/to/document.pdf;type=application/pdf" \
-F "model=mistral-ocr-latest"
# Upload a local image
curl https://llm.rooyai.com/v1/ocr/upload \
-H "Authorization: Bearer sk-your-api-key" \
-F "file=@/path/to/scan.png;type=image/png" \
-F "model=mistral-ocr-latest"
# Upload with page range and table extraction
curl https://llm.rooyai.com/v1/ocr/upload \
-H "Authorization: Bearer sk-your-api-key" \
-F "file=@/path/to/report.pdf;type=application/pdf" \
-F "model=mistral-ocr-latest" \
-F "pages=0-5" \
-F "table_format=markdown"Example Response
{
"model": "mistral-ocr-latest",
"pages": [
{
"index": 0,
"markdown": "# Invoice\n\n| Item | Price |\n|------|-------|\n| Widget | $9.99 |",
"dimensions": { "dpi": 300, "height": 1056, "width": 816 },
"images": []
}
],
"usage_info": {
"pages_processed": 1,
"doc_size_bytes": 45231
}
}SDK Examples
import requests
BASE_URL = "https://llm.rooyai.com"
API_KEY = "sk-your-api-key"
headers = {"Authorization": f"Bearer {API_KEY}"}
# ── Option A: OCR a PDF from a URL ──────────────────────────────────────────
response = requests.post(
f"{BASE_URL}/v1/ocr",
headers={**headers, "Content-Type": "application/json"},
json={
"model": "mistral-ocr-latest",
"document": {
"type": "document_url",
"document_url": "https://example.com/document.pdf",
},
"table_format": "markdown", # optional
},
timeout=300,
)
data = response.json()
print(f"Pages processed: {data['usage_info']['pages_processed']}")
for page in data["pages"]:
print(f"\n--- Page {page['index'] + 1} ---")
print(page["markdown"]) # full Markdown for this page
# ── Option B: Upload a local file and OCR ───────────────────────────────────
with open("/path/to/document.pdf", "rb") as f:
upload_response = requests.post(
f"{BASE_URL}/v1/ocr/upload",
headers=headers,
files={"file": ("document.pdf", f, "application/pdf")},
data={
"model": "mistral-ocr-latest",
"pages": "0-9", # first 10 pages only (optional)
},
timeout=300,
)
upload_data = upload_response.json()
full_text = "\n\n".join(p["markdown"] for p in upload_data["pages"])
print(full_text)Billing
OCR is billed by pages processed, not tokens. Each page in the response increments the counter regardless of page content size.
cost = pages_processed × (input_price_per_1M ÷ 1,000,000)pages parameter to process only the pages you need — e.g. "pages": [0, 1, 2] for the first 3 pages only. This reduces both cost and processing time for large documents.429. Large documents may take up to 700 seconds — set your HTTP client timeout accordingly.Error Responses
| Status | Reason |
|---|---|
400 | Invalid request body, missing required fields, or unsupported document type |
401 | Invalid or missing API key |
402 | Insufficient credits or budget limit exceeded |
413 | File exceeds the 50 MB limit (upload endpoint) |
415 | Unsupported file type (upload endpoint — send PDF, JPEG, PNG, WebP, TIFF, BMP, or GIF) |
429 | Too many concurrent OCR requests — retry in a moment |
503 | OCR service not configured on this gateway |
504 | OCR timed out — use the pages parameter to process fewer pages at once |
Image Generation
NEWGenerate images from text prompts using diffusion models like FLUX.1 and others. Fully OpenAI-compatible — just change base_url and api_key in your existing image generation code.
output_format ("jpeg" or "webp") in your request body and the gateway converts the image after the model responds — no extra cost, no client-side processing needed. Use output_quality (1–100) to control compression./v1/images/generationsapplication/jsonAuth requiredRequest Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
prompt | string | ✓ | — | Text description of the image to generate. Be descriptive for best results. |
model | string | ✓ | — | Image model ID. Use GET /v1/models to list all available image generation models. |
n | integer | — | 1 | Number of images to generate (1–10). |
size | string | — | 1024x1024 | Output dimensions. Common values: 1024x1024, 1792x1024, 1024x1792. Support varies by model. |
response_format | string | — | b64_json | "b64_json" (recommended) or "url". Use b64_json to get the image bytes directly. |
quality | string | — | — | Model quality hint (e.g. "hd"). Support and accepted values vary by model. |
style | string | — | — | Style hint (e.g. "vivid", "natural"). Support varies by model. |
output_format | string | — | — | [Gateway-only] Convert the returned image to "png", "jpeg", or "webp". Applied after the upstream responds — no extra cost. |
output_quality | integer | — | 85 | [Gateway-only] Compression quality (1–100) for jpeg/webp output_format. Ignored for png. Default: 85. |
Common Image Sizes
Supported sizes vary by model. Always check the model documentation or test with your chosen model.
| size | Aspect Ratio | Notes |
|---|---|---|
1024×1024 | 1:1 | Square. Default and most widely supported. |
1792×1024 | 16:9 | Landscape / widescreen. |
1024×1792 | 9:16 | Portrait / tall. |
512×512 | 1:1 | Smaller square. Supported by some models only. |
Gateway-Side Output Formats
Set output_format to convert the returned image on the gateway before delivery. No additional model call is made.
| Format | MIME Type | Notes |
|---|---|---|
png | image/png | Lossless. Default output from most diffusion models. |
jpeg | image/jpeg | Lossy. Smaller files. Good for web delivery. Use output_quality to tune. |
webp | image/webp | Lossy (with lossless option). Excellent for modern browsers. Use output_quality to tune. |
Live Endpoint Tester
Example Requests (cURL)
# ── 1. Standard PNG — save directly to file ──────────────────────────────
curl https://llm.rooyai.com/v1/images/generations \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "flux-schnell",
"prompt": "a majestic mountain at golden hour, photorealistic, 8k",
"n": 1,
"size": "1024x1024",
"response_format": "b64_json"
}' | python3 -c "
import sys, json, base64
d = json.load(sys.stdin)
open('output.png', 'wb').write(base64.b64decode(d['data'][0]['b64_json']))
print('Saved output.png')
"
# ── 2. JPEG with 80% quality (gateway converts for you) ───────────────────
curl https://llm.rooyai.com/v1/images/generations \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "flux-schnell",
"prompt": "aerial view of a tropical island, turquoise water, cinematic",
"n": 1,
"size": "1792x1024",
"response_format": "b64_json",
"output_format": "jpeg",
"output_quality": 80
}' | python3 -c "
import sys, json, base64
d = json.load(sys.stdin)
open('output.jpg', 'wb').write(base64.b64decode(d['data'][0]['b64_json']))
print('Saved output.jpg')
"
# ── 3. WebP — smallest file size, great for web delivery ──────────────────
curl https://llm.rooyai.com/v1/images/generations \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "flux-schnell",
"prompt": "futuristic city skyline at night, neon lights, ultra detailed",
"n": 1,
"size": "1024x1024",
"response_format": "b64_json",
"output_format": "webp",
"output_quality": 90
}' | python3 -c "
import sys, json, base64
d = json.load(sys.stdin)
open('output.webp', 'wb').write(base64.b64decode(d['data'][0]['b64_json']))
print('Saved output.webp')
"
# ── NOTE: n and output_quality MUST be numbers, not strings ───────────────
# ✅ Correct: "n": 1, "output_quality": 80
# ❌ Wrong: "n": "1", "output_quality": "80" (causes 400 validation error)
# In n8n: use "Send Body" → "JSON" mode, NOT key-value/form mode.Example Response
The _format and _mime_type fields are only present when output_format was specified.
{
"created": 1714000000,
"data": [
{
"b64_json": "iVBORw0KGgoAAAANSUh…[base64-encoded image]",
"_format": "jpeg",
"_mime_type": "image/jpeg"
}
],
"_output_format": "jpeg",
"_mime_type": "image/jpeg"
}SDK Examples
import base64
import requests
from pathlib import Path
from openai import OpenAI
BASE_URL = "https://llm.rooyai.com"
API_KEY = "sk-your-api-key"
client = OpenAI(base_url=f"{BASE_URL}/v1", api_key=API_KEY)
# ── Option A: Standard PNG via OpenAI SDK ────────────────────────────────────
response = client.images.generate(
model="flux-schnell",
prompt="a majestic mountain at golden hour, photorealistic, 8k",
n=1, # integer — NOT a string
size="1024x1024",
response_format="b64_json",
)
Path("output.png").write_bytes(base64.b64decode(response.data[0].b64_json))
print("Saved output.png")
# ── Option B: JPEG with quality control (via requests — OpenAI SDK strips
# unknown fields like output_format / output_quality) ────────────
resp = requests.post(
f"{BASE_URL}/v1/images/generations",
headers={"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"},
json={
"model": "flux-schnell",
"prompt": "aerial view of a tropical island, turquoise water, cinematic",
"n": 1, # integer
"size": "1792x1024",
"response_format": "b64_json",
"output_format": "jpeg",
"output_quality": 80, # integer 1–100
},
timeout=120,
)
resp.raise_for_status()
data = resp.json()
img_bytes = base64.b64decode(data["data"][0]["b64_json"])
Path("output.jpg").write_bytes(img_bytes)
print(f"Saved output.jpg ({len(img_bytes) // 1024} KB)")
# ── Option C: WebP (smallest for web delivery) ────────────────────────────
resp = requests.post(
f"{BASE_URL}/v1/images/generations",
headers={"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"},
json={
"model": "flux-schnell",
"prompt": "futuristic city skyline at night, neon lights, ultra detailed",
"n": 1,
"size": "1024x1024",
"response_format": "b64_json",
"output_format": "webp",
"output_quality": 90,
},
timeout=120,
)
resp.raise_for_status()
data = resp.json()
Path("output.webp").write_bytes(base64.b64decode(data["data"][0]["b64_json"]))
print("Saved output.webp")
# ── Option D: Generate multiple images concurrently (asyncio + httpx) ────────
# The gateway supports up to 20 concurrent image gen requests per API key.
# Use asyncio to fire them in parallel — total wall time ≈ 1 image time.
import asyncio, httpx
async def generate_image(client: httpx.AsyncClient, prompt: str, index: int):
resp = await client.post(
f"{BASE_URL}/v1/images/generations",
json={
"model": "flux-schnell",
"prompt": prompt,
"n": 1,
"size": "1024x1024",
"response_format": "b64_json",
"output_format": "jpeg",
"output_quality": 80,
},
timeout=120,
)
resp.raise_for_status()
data = resp.json()
Path(f"batch_{index}.jpg").write_bytes(
base64.b64decode(data["data"][0]["b64_json"])
)
print(f"Saved batch_{index}.jpg")
async def main():
prompts = [
"a red rose in the rain, macro photography",
"a golden desert dune at sunrise, epic scale",
"a futuristic Tokyo street at night, neon rain",
]
headers = {"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"}
async with httpx.AsyncClient(headers=headers) as client:
await asyncio.gather(*[
generate_image(client, p, i) for i, p in enumerate(prompts)
])
print("All images saved!")
asyncio.run(main())Billing
Image generation is billed per image (per n), not per token. Cost is looked up from the model's input_price_per_1M field:
cost = n × (input_price_per_1M ÷ 1,000,000)output_format / output_quality) is performed after billing — only the original image generation is charged. Converting PNG → JPEG or WebP incurs no additional API cost.- Be descriptive in your prompt — include style, lighting, composition, and quality hints.
- Add keywords like photorealistic, 8k, cinematic, ultra detailed for higher quality outputs.
- Use
output_format: "webp"for web delivery — typically 25-35% smaller than PNG at near-identical quality. - Use
output_quality: 75-85for JPEG/WebP — a good balance of size vs quality.
Error Responses
| Status | Reason |
|---|---|
400 | Missing or invalid request field (e.g. empty prompt, unsupported size) |
401 | Invalid or missing API key |
402 | Insufficient credits or budget limit exceeded |
429 | Rate limit or concurrent request limit exceeded |
503 | Service temporarily unavailable (circuit breaker open) |
504 | Image generation timed out — the model took too long to respond |
Health Check
Server health endpoint for monitoring and load balancers.
/health{
"status": "ok",
"timestamp": "2026-04-25T02:00:00.000Z",
"uptime": 86400,
"memory": { "rss": 128, "heap": 64 }
}Streaming (SSE)
Set "stream": true to receive responses as Server-Sent Events in real time.
How It Works
- The server responds with
Content-Type: text/event-stream - Each chunk is a
data:line containing a JSON delta - The stream terminates with
data: [DONE]
cURL Example
curl https://llm.rooyai.com/v1/chat/completions \\
-H "Authorization: Bearer sk-your-api-key" \\
-H "Content-Type: application/json" \\
-d '{
"model": "<your-chat-model>",
"messages": [{"role": "user", "content": "Count to 5"}],
"stream": true
}'Tool Calling
NEWTool calling (also called function calling) lets the model invoke your own functions during a conversation. The model decides when a tool is relevant, returns a structured invocation request, and you execute it — then pass the result back for a final human-readable response.
GET /v1/models for the full list of available models. Models that do not support tool calling will return a normal text response and ignore the tools field.How It Works
- You define tools — describe functions as JSON Schema objects in the
toolsarray. - Model decides — if the user's message warrants a tool call, the response has
finish_reason: "tool_calls"and atool_callsarray. - You execute — call your actual function with the arguments the model provided.
- Send results back — append a
role: "tool"message with the result and make a second API call. - Final response — the model synthesises your tool result into a natural-language reply.
Request Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
tools | array | — | - | Array of tool definitions the model can call. Each entry has type=function and a function object. |
tool_choice | string | object | — | "auto" | "none" disables tool calling, "auto" lets the model decide, "required" forces a tool call, or { type: "function", function: { name } } to force a specific tool. |
parallel_tool_calls | boolean | — | true | Whether the model may invoke multiple tools in a single response turn. |
Tool Definition Fields
Each entry in the tools array:
| Parameter | Type | Required | Description |
|---|---|---|---|
type | "function" | ✓ | Always "function" - the only tool type currently supported. |
function.name | string | ✓ | Name of the function. Use snake_case (e.g. get_weather). |
function.description | string | — | Human-readable description of what the function does. Helps the model decide when to call it. |
function.parameters | object | — | JSON Schema object describing the function's parameters. Use 'required' array to mark mandatory args. |
Tool Call Response Fields
When the model invokes a tool, each item in message.tool_calls contains:
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | ✓ | Unique ID for this tool call. Must be passed back as tool_call_id in the follow-up message. |
type | "function" | ✓ | Always "function". |
function.name | string | ✓ | Name of the function the model wants to call. |
function.arguments | string | ✓ | JSON-encoded string containing the argument values. Parse with JSON.parse() before calling your function. |
Basic Example — cURL
curl https://llm.rooyai.com/v1/chat/completions \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "<your-chat-model>",
"messages": [
{"role": "system", "content": "You are a tool-calling assistant. Call tools when relevant."},
{"role": "user", "content": "What is the weather in Cairo right now?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto",
"parallel_tool_calls": true,
"temperature": 0.2
}'Response
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"model": "<model-id>",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_xyz789",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Cairo\", \"unit\": \"celsius\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 95,
"completion_tokens": 22,
"total_tokens": 117
}
}Full Multi-Turn Example
A complete 4-step agentic loop: define tools → first call → execute function → send result back.
from openai import OpenAI
import json
client = OpenAI(
base_url="https://llm.rooyai.com/v1",
api_key="sk-your-api-key"
)
# -- Step 1: Define your tools ----------------------------
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
]
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the weather in Cairo?"}
]
# -- Step 2: First call - model returns tool_calls --------
response = client.chat.completions.create(
model="<your-chat-model>",
messages=messages,
tools=tools,
tool_choice="auto"
)
assistant_msg = response.choices[0].message
print(f"finish_reason: {response.choices[0].finish_reason}")
# Output: finish_reason: tool_calls
# -- Step 3: Execute your real function ------------------
def get_weather(city, unit="celsius"):
return {"city": city, "temperature": 32, "unit": unit, "condition": "sunny"}
tool_results = []
for tc in assistant_msg.tool_calls:
args = json.loads(tc.function.arguments)
result = get_weather(**args)
tool_results.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result)
})
# -- Step 4: Second call - send results back --------------
messages.append(assistant_msg)
messages.extend(tool_results)
final = client.chat.completions.create(
model="<your-chat-model>",
messages=messages,
tools=tools
)
print(final.choices[0].message.content)
# Output: "The current weather in Cairo is 32degC and sunny."Parallel Tool Calls
Set parallel_tool_calls: true to let the model invoke multiple tools in a single response — useful for gathering independent data sources simultaneously.
finish_reason Values
| finish_reason | Meaning |
|---|---|
stop | Normal text response — no tool was called. |
tool_calls | Model wants to invoke one or more tools. Parse message.tool_calls. |
length | Response was cut off by max_tokens — retry with a higher limit. |
content_filter | Response blocked by content policy. |
finish_reason before reading message.content. When it is tool_calls, message.content will be null.Vision / Images
NEWSend images alongside text in any chat completion request. Vision-capable models can analyse, describe, compare, and reason about images from a public URL or an inline base64-encoded file.
GET /v1/models to check which models are available on your gateway. Text-only models will return a 400 error if you include images — check the model's capabilities before sending.How It Works
Instead of passing content as a plain string, pass it as an array of content parts. Each part is either a text part or an image_url part. You can mix multiple text and image parts in any order.
Content Part Types
| Part type | Required fields | Description |
|---|---|---|
text | type, text | A plain text segment. Equivalent to a normal string message. |
image_url | type, image_url.url | An image. url is either a public HTTPS URL or a base64 data URL (data:image/jpeg;base64,…). |
Image Detail Level
Set image_url.detail to control quality vs token cost. Defaults to "auto".
| detail | Behaviour | Token cost |
|---|---|---|
auto | Model decides based on image size (recommended) | Varies |
low | Resizes to 512×512 — fast and cheap | ~85 tokens flat |
high | Full resolution with tiled analysis — most accurate | Higher — depends on image size |
Example — Image from URL (cURL)
curl https://llm.rooyai.com/v1/chat/completions \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "<your-chat-model>",
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "What is in this image?" },
{
"type": "image_url",
"image_url": {
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/280px-PNG_transparency_demonstration_1.png",
"detail": "auto"
}
}
]
}
],
"max_tokens": 300
}'Example — Inline Base64 Image (cURL)
curl https://llm.rooyai.com/v1/chat/completions \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "<your-chat-model>",
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "Describe this image in detail." },
{
"type": "image_url",
"image_url": {
"url": "data:image/jpeg;base64,<YOUR_BASE64_STRING>"
}
}
]
}
]
}'SDK Examples
from openai import OpenAI
import base64
client = OpenAI(
base_url="https://llm.rooyai.com/v1",
api_key="sk-your-api-key"
)
# ── Option A: Image URL ──────────────────────────────────────────
response = client.chat.completions.create(
model="<your-chat-model>",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What is in this image?"},
{
"type": "image_url",
"image_url": {
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/280px-PNG_transparency_demonstration_1.png",
"detail": "auto" # "auto", "low", or "high"
}
}
]
}
],
max_tokens=300
)
print(response.choices[0].message.content)
# ── Option B: Local file as base64 ──────────────────────────────
with open("image.jpg", "rb") as f:
image_data = base64.b64encode(f.read()).decode("utf-8")
response = client.chat.completions.create(
model="<your-chat-model>",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Describe this image."},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image_data}"}
}
]
}
]
)
print(response.choices[0].message.content)Base64-Only Examples (Local File)
Use these when you have a local image file that is not publicly accessible on the internet. The image is read from disk, encoded to a data:image/…;base64,… data URL, and sent inline in the request body.
from openai import OpenAI
import base64
client = OpenAI(
base_url="https://llm.rooyai.com/v1",
api_key="sk-your-api-key"
)
# Read the image file and encode it to a base64 data URL
with open("image.jpg", "rb") as f:
image_data = base64.b64encode(f.read()).decode("utf-8")
# Build the data URL — the gateway accepts any image MIME type
data_url = f"data:image/jpeg;base64,{image_data}"
response = client.chat.completions.create(
model="<your-chat-model>",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What is in this image?"},
{
"type": "image_url",
"image_url": {
"url": data_url,
"detail": "auto" # "auto" | "low" | "high"
}
}
]
}
],
max_tokens=300
)
print(response.choices[0].message.content)
# usage.prompt_tokens will include the image token cost
print(f"Tokens used: {response.usage.prompt_tokens} prompt / {response.usage.completion_tokens} completion")- Use public URLs when possible — no payload overhead.
- Use base64 for private images or local files. Prefix:
data:image/jpeg;base64,… - Supported formats: JPEG, PNG, GIF, WebP.
- Images count against your prompt tokens. Use
detail: "low"for large images when high accuracy is not needed. - You can include multiple images in a single message — just add more
image_urlparts.
Rate Limiting
The gateway enforces rate limits at two levels:
1. IP-Based Rate Limit
120 requests per minute per IP address. Applies to all requests regardless of authentication.
2. Per-Key Rate Limits
Each API key has configurable limits set at creation time:
| Limit | Free | Pro | Enterprise |
|---|---|---|---|
| Requests per minute (RPM) | 60 | 300 | 10,000 |
| Tokens per day (TPD) | 100,000 | 1,000,000 | 100,000,000 |
Rate Limit Headers
X-RateLimit-Limit-RPM: 60
X-RateLimit-Remaining-RPM: 55
X-RateLimit-Limit-TPD: 100000
X-RateLimit-Remaining-TPD: 98500When Rate Limited
{
"error": {
"message": "Rate limit exceeded",
"type": "rate_limit_error"
}
}HTTP Status: 429 Too Many Requests · Header: Retry-After: <seconds>
Budget Controls
API keys can have an optional budget limit to cap spending.
- Set during key creation in the dashboard
- Enforced in real time via Redis
- When exceeded, requests return
402 Payment Required
{
"error": {
"message": "Budget limit exceeded for this API key",
"type": "budget_exceeded"
}
}Error Handling
All errors follow a consistent format:
{
"error": {
"message": "Human-readable error description",
"type": "error_type"
}
}Error Types & HTTP Status Codes
| Status | Type | Description |
|---|---|---|
400 | invalid_request_error | Malformed request body or invalid parameters |
401 | invalid_request_error | Missing, invalid, inactive, or expired API key |
402 | budget_exceeded | API key budget limit reached |
429 | rate_limit_error | RPM/TPD or IP rate limit exceeded |
500 | server_error | Internal server error |
504 | timeout_error | Request timed out (30s default, 300s for streams) |
SDK Integration
Python
pip install openaifrom openai import OpenAI
client = OpenAI(
base_url="https://llm.rooyai.com/v1",
api_key="sk-your-api-key"
)
# Chat completion
response = client.chat.completions.create(
model="<your-chat-model>",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is machine learning?"}
],
temperature=0.7,
max_tokens=500
)
print(response.choices[0].message.content)
# Embeddings
embedding = client.embeddings.create(
model="<your-embedding-model>",
input="Hello world"
)
print(f"Dimensions: {len(embedding.data[0].embedding)}")JavaScript / TypeScript
npm install openaiimport OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://llm.rooyai.com/v1',
apiKey: 'sk-your-api-key',
});
// Chat completion
const response = await client.chat.completions.create({
model: '<your-chat-model>',
messages: [{ role: 'user', content: 'Explain REST APIs' }],
max_tokens: 500,
});
console.log(response.choices[0].message.content);
// Streaming
const stream = await client.chat.completions.create({
model: '<your-chat-model>',
messages: [{ role: 'user', content: 'Hello!' }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || '');
}cURL
# Chat completion
curl https://llm.rooyai.com/v1/chat/completions \\
-H "Authorization: Bearer sk-your-api-key" \\
-H "Content-Type: application/json" \\
-d '{
"model": "<your-chat-model>",
"messages": [{"role": "user", "content": "Hello!"}]
}'
# List models (no auth required)
curl https://llm.rooyai.com/v1/models
# Embeddings
curl https://llm.rooyai.com/v1/embeddings \\
-H "Authorization: Bearer sk-your-api-key" \\
-H "Content-Type: application/json" \\
-d '{
"model": "<your-embedding-model>",
"input": "Hello world"
}'Best Practices
1. Use Streaming for Long Responses
Streaming provides a better UX and avoids timeouts for long completions. The stream timeout is 300 seconds vs 30 seconds for non-streaming.
2. Set Budget Limits
Protect against runaway costs by setting a budget limit on each API key in the dashboard.
3. Handle Rate Limits Gracefully
Implement exponential backoff when you receive 429 responses:
import time
def call_with_retry(fn, max_retries=3):
for attempt in range(max_retries):
try:
return fn()
except RateLimitError:
wait = 2 ** attempt
time.sleep(wait)
raise Exception("Max retries exceeded")4. Monitor Your Usage
Check the Dashboard for real-time cost tracking, per-model breakdowns, and request volume charts.
5. Rotate Keys Periodically
Create new keys and deactivate old ones regularly. Manage all keys from the dashboard without downtime.
6. Check Model Health Before Calling
Use GET /v1/models/status to check model availability before making requests.
Available Models
The table below is fetched live from your gateway. Use GET /v1/models for the authoritative up-to-date list at any time.
| Model Type | Description |
|---|---|
| Chat / Instruction | Large language models for conversational tasks, reasoning, and generation. |
| Text Completion | Legacy completion models for prompt-based text generation. |
| Embeddings | Vector embedding models for semantic search and similarity. |
| Speech-to-Text (ASR) | Audio transcription models via /v1/audio/transcriptions. |
| Image Generation | Diffusion-based image models (FLUX, SDXL, etc.) via /v1/images/generations. |
GET /v1/models| Request Type | Timeout |
|---|---|
Non-streaming requests | 30 seconds |
Streaming requests | 300 seconds (5 minutes) |
| Header | Description |
|---|---|
X-RateLimit-Limit-RPM | Your RPM limit for this key |
X-RateLimit-Remaining-RPM | Remaining requests this minute |
X-RateLimit-Limit-TPD | Your TPD limit for this key |
X-RateLimit-Remaining-TPD | Remaining tokens today |
X-Request-ID | Unique request identifier for debugging |
Retry-After | Seconds to wait (on 429 responses) |
RooyaLLM — One API for Every AI Model