BetopiaAI API / v1
API documentation
Use the BetopiaAI API for chat, embeddings, image generation, and image editing with models available to your workspace. Each operation has a request contract suited to its output.
Base URL
https://api.betopia.ai/v1
Quickstart
Create an API key, add it as a Bearer token, and send a request to the chat completions endpoint.
curl https://api.betopia.ai/v1/chat/completions \ -H "Authorization: Bearer sk_your_key" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "messages": [{"role": "user", "content": "Hello"}] }'Authentication
Inference endpoints use an API key. Account, key-management, analytics, and billing endpoints use the JWT returned by login. Keep both credentials on your server.
Authorization: Bearer sk_your_keyPOST https://api.betopia.ai/v1/auth/loginContent-Type: application/json{"email":"you@example.com","password":"your_password"}→ {"token":"eyJ..."}Chat completions
Send an ordered list of messages. Use auto for platform routing or a model ID from the model catalog.
curl https://api.betopia.ai/v1/chat/completions \ -H "Authorization: Bearer sk_your_key" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "messages": [ {"role": "system", "content": "Be concise."}, {"role": "user", "content": "Summarize this release."} ], "temperature": 0.2, "max_tokens": 300 }'Responses
Use Responses for structured output, tools, reasoning configuration, and continuation state. The response contains an output array and token usage.
curl https://api.betopia.ai/v1/responses \ -H "Authorization: Bearer sk_your_key" \ -H "Content-Type: application/json" \ -d '{ "model": "auto", "input": "Draft a product update.", "max_output_tokens": 150 }'Streaming
Set stream: true on chat or Responses requests. The server returns Server-Sent Events and finishes with data: [DONE].
data: {"choices":[{"delta":{"content":"Hello"}}]}
data: {"choices":[{"delta":{"content":" world"}}]}
data: [DONE]
Tools
Pass function definitions in the Responses request. Check the model capability flags before depending on tools, parallel tool calls, reasoning, JSON schema, or vision.
POST https://api.betopia.ai/v1/responsesAuthorization: Bearer sk_your_keyContent-Type: application/json{ "model": "auto", "input": "Find the weather in Dhaka.", "tools": [{ "type": "function", "name": "get_weather", "description": "Get current weather for a city.", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"] } }]}Embeddings and images
These operations use separate endpoints, not chat completions. Select a concrete model whose verified capability flag is enabled; autois only for chat and Responses. Discover each model's image limits, formats, defaults, and exact size/quality variants before sending a generation request.
curl https://api.betopia.ai/v1/embeddings \ -H "Authorization: Bearer sk_your_key" \ -H "Content-Type: application/json" \ -d '{"model":"embedding-model-id","input":"One document to embed"}'const response = await fetch("https://api.betopia.ai/v1/embeddings", { method: "POST", headers: { Authorization: "Bearer sk_your_key", "Content-Type": "application/json" }, body: JSON.stringify({ model: "embedding-model-id", input: "One document to embed" }),});const result = await response.json();if (!response.ok) throw new Error(`${response.status}: ${result.error ?? result.message}`);console.log(`Vector dimensions: ${result.data[0].embedding.length}`);For a batch, pass an array of strings. The response returns one indexed vector per input.
curl https://api.betopia.ai/v1/embeddings \ -H "Authorization: Bearer sk_your_key" \ -H "Content-Type: application/json" \ -d '{"model":"embedding-model-id","input":["First document","Second document"]}'const response = await fetch("https://api.betopia.ai/v1/embeddings", { method: "POST", headers: { Authorization: "Bearer sk_your_key", "Content-Type": "application/json" }, body: JSON.stringify({ model: "embedding-model-id", input: ["First document", "Second document"] }),});const result = await response.json();if (!response.ok) throw new Error(`${response.status}: ${result.error ?? result.message}`);console.log(`Received ${result.data.length} vectors`);This response includes operation_schema (schema version, maximum image count, allowed output formats/backgrounds, and default format). Image generation currently supports fixed per-image pricing; use the exact size/quality pairs in prices. A legacy token-priced configuration may return supported_variants, but generation is unavailable until an admin switches that model to per-image pricing. The endpoint enforces the same model access policy as inference.
curl https://api.betopia.ai/v1/models/<model-id>/image-generation-prices \ -H "Authorization: Bearer sk_your_key"Normalized user request: the JSON fields below are Betopia's shared image-generation contract and keep the same names and types across providers. The platform adapter translates them upstream; actual option support depends on the selected provider/model. Admin-only provider parameters configure upstream mapping and do not change the fields users send or the platform price matrix.
curl https://api.betopia.ai/v1/images/generations \ -H "Authorization: Bearer sk_your_key" \ -H "Content-Type: application/json" \ -d '{"model":"image-model-id","prompt":"A paper-cut mountain landscape","n":1,"size":"<supported-size>","quality":"<supported-quality>","resolution":"2K","aspect_ratio":"16:9","output_format":"webp","output_compression":80,"seed":42}'const response = await fetch("https://api.betopia.ai/v1/images/generations", { method: "POST", headers: { Authorization: "Bearer sk_your_key", "Content-Type": "application/json", "Idempotency-Key": "image-job-001" }, body: JSON.stringify({ model: "image-model-id", prompt: "A paper-cut mountain landscape", n: 1, size: "<configured-size>", quality: "<configured-quality>", resolution: "2K", aspect_ratio: "16:9", output_format: "webp", output_compression: 80, seed: 42 }),});const result = await response.json();if (!response.ok) throw new Error(`${response.status}: ${result.error ?? result.message}`);console.log(`Generated ${result.data.length} image(s)`); // Keep base64 data out of logs and persistent storage.The JSON body is limited to 10 MiB; unlisted fields are rejected. When resolution/aspect_ratio is set, size remains the platform price selector and is omitted from the upstream request. Read the operation schema and price matrix first. Image generation currently supports fixed per-image billing only; token-priced generation is unavailable, and a model with a legacy token-pricing configuration may return 503 until an admin switches it to per-image pricing. Streaming events are image_generation.partial_image, image_generation.completed, and [DONE]; failures after stream start use an error event. The response reports the final charge in usage.cost_usd.
curl https://api.betopia.ai/v1/images/edits \ -H "Authorization: Bearer sk_your_key" \ -F "model=image-edit-model-id" -F "prompt=Replace the cloudy sky with sunset" \ -F "size=<configured-size>" -F "quality=<configured-quality>" \ -F "image[]=@source.png"import { readFile } from "node:fs/promises";const image = await readFile("./source.png");const form = new FormData();form.set("model", "image-edit-model-id");form.set("prompt", "Replace the cloudy sky with a clear sunset");form.set("size", "<configured-size>"); form.set("quality", "<configured-quality>");form.set("image[]", new Blob([image], { type: "image/png" }), "source.png");const response = await fetch("https://api.betopia.ai/v1/images/edits", { method: "POST", headers: { Authorization: "Bearer sk_your_key" }, body: form });const result = await response.json();if (!response.ok) throw new Error(`${response.status}: ${result.error ?? result.message}`);console.log(`Received ${result.data.length} edited image(s)`); // Do not log or persist the base64 output.Send only the fields above as multipart/form-data; extra or duplicate fields are rejected. Edits do not accept n, output_format, or background, and return one edited image. Admins configure provider-specific options per exact size/quality rule; for OpenRouter models, those saved options (for example resolution and aspect_ratio) are translated into OpenRouter's image-to-image request. This keeps the user request identical across providers. A configured platform price is separate from the provider's charge. To use a mask, add -F "mask=@mask.png" only for a provider that supports masks; uploads are not persisted.
Do not persist prompts, uploaded images, or large base64 outputs in browser storage or analytics. See the API Guide for field limits, exact pricing rules, and error behavior.
Models
The authenticated catalog includes models available to the current caller. Model records include operation capability flags and token limits. Use flags only when capabilities_configured is true; `auto` is only for chat and Responses.
GET https://api.betopia.ai/v1/modelsAuthorization: Bearer sk_your_key→ { "models": [{ "id": "auto", "name": "auto", "provider": "betopia", "capabilities_configured": false, "available": true }], "count": 1}curl https://api.betopia.ai/v1/models \ -H "Authorization: Bearer sk_your_key"Filter for a capability only after capabilities_configured is true; use each returned model ID explicitly for embedding and image calls.
const response = await fetch("https://api.betopia.ai/v1/models", { headers: { Authorization: "Bearer sk_your_key" } });const { models } = await response.json();const embeddingModels = models.filter((model) => model.capabilities_configured === true && model.capabilities?.embeddings === true);const imageModels = models.filter((model) => model.capabilities_configured === true && model.capabilities?.image_generation === true);console.log(embeddingModels.map(({ id, name }) => ({ id, name })));const imageModel = imageModels[0];if (imageModel) { const optionsResponse = await fetch(`https://api.betopia.ai/v1/models/${encodeURIComponent(imageModel.id)}/image-generation-prices`, { headers: { Authorization: "Bearer sk_your_key" } }); if (!optionsResponse.ok) throw new Error(`Could not read image options: ${optionsResponse.status}`); const options = await optionsResponse.json(); console.log(options.operation_schema, options.prices ?? options.supported_variants);}API keys
Keys are created with a JWT. The full secret is returned only at creation time; list responses expose the prefix. Revoke keys you no longer need.
POST https://api.betopia.ai/v1/keysAuthorization: Bearer eyJ...Content-Type: application/json{"name":"production","expires_in_days":0,"token_limit":0,"allowed_models":[]}→ {"id":"...","key":"sk_...","key_prefix":"..."}Usage and limits
Read the current quota snapshot or query usage analytics by day, month, model, key, and audit event.
curl https://api.betopia.ai/v1/user/limits \ -H "Authorization: Bearer sk_your_key"const response = await fetch("https://api.betopia.ai/v1/images/generations", { method: "POST", headers: { Authorization: "Bearer sk_your_key", "Content-Type": "application/json" }, body: JSON.stringify({ model: "image-model-id", prompt: "A simple landscape", size: "<configured-size>", quality: "<configured-quality>" }),});const payload = await response.json().catch(() => ({}));let requestError;if (response.status === 429) requestError = new Error("Quota reached: " + (payload.message ?? "request denied") + "; reset " + (payload.reset_at ?? "not scheduled"));else if (!response.ok) requestError = new Error(response.status + " request " + (response.headers.get("X-Request-Id") ?? "unknown") + ": " + (payload.error ?? payload.message));const limits = await fetch("https://api.betopia.ai/v1/user/limits", { headers: { Authorization: "Bearer sk_your_key" } });if (limits.ok) console.log(await limits.json()); // cost_limit_usd, used_usd, remaining_usd, quota_period, reset_atif (requestError) throw requestError;Coding Agent access
The Coding Agent supports two explicit billing modes. Choose subscription sign-in for plan-limited access, or enter a general API key for PAYG wallet access. These credentials and routes are not interchangeable.
# Subscription credential: use only the Coding Agent gatewaycurl https://api.betopia.ai/v1/coding-agent/models \ -H "Authorization: Bearer sk_ca_your_subscription_key"# Manual general API key: PAYG wallet, normal API accesscurl https://api.betopia.ai/v1/models \ -H "Authorization: Bearer sk_your_general_key"The daily counter resets at the server-configured timezone; monthly usage resets on the Stripe billing anniversary. The user interface shows percentages only, not the USD caps or consumed USD amount.
Errors
Failed requests return JSON with an error message. When available, keep the request ID with your logs for troubleshooting.
{ "error": "invalid or expired api key", "request_id": "req_01hzy..."}Endpoints
All paths are relative to the base URL shown in the navigation.