API reference
VIABLE API
Every public endpoint with its request fields, response shape, and copy-ready examples, generated from OpenAPI 2.0.0 so it always matches the contract.
- Base URL
- Authentication
Authorization: Bearer $VIABLE_API_KEY- Compatibility
- OpenAI request and response shapes
Introduction
The VIABLE API is a model-first, OpenAI-compatible API operated by VIABLE Lab. One key and one base URL reach chat, multimodal input, published Agents, embeddings, image generation, speech, and transcription across frontier and open-weight models from several providers.
- OpenAI SDKs work unchanged once
base_urlishttps://api.viablelab.org/v1. Gemini models use the same OpenAI-compatible shape; Gemini-native routes are not exposed. - Send a catalog ID or a VIABLE alias in
model. Routing, provider translation, cost estimation, and logging happen behind it. - Every authenticated request lands in your Request Logs with status, tokens, estimated cost, and the logged payloads.
- VETTING (Verification and Evaluation Tool for Targeting Invalid Narrative Generation) has a second model check each chat reply against your guidance or an answer key before it is returned. It was evaluated in a peer-reviewed classroom study.
Coding agents
Let your agent set it up
Paste this into Claude Code, Codex, or any agent with a terminal. It installs the VIABLE CLI and its Agent Skill, then helps you sign in in your browser. It never asks for your password.
Read https://www.viablelab.org/downloads/viable-cli.md and set up VIABLE CLI and its Agent Skill for your environment. Help me sign in securely.Needs terminal access and Node.js 22+.Setup detailsNo terminal? Share the API guide
Authentication and API keys
Every protected request carries a VIABLE API key as a bearer token. Keys belong to a project, are created in the dashboard (or by the CLI), and stay on your server.
RecommendedCreate one key per app or environment, give it only the scopes it needs, and call the API from your backend. Browser and mobile clients should call your server, never the API directly.
Authorization: Bearer $VIABLE_API_KEYon every protected request.X-API-Keyis also accepted.- Scopes:
model:requestfor model endpoints,agents:executefor published Agents,utils:cryptofor encryption utilities. - Optional limits: requests per minute, daily token cap, monthly cost cap, and an expiration date.
allowedModelsrestricts a key to exact model IDs or aliases.
- Public GET endpoints such as
/v1/modelsand/v1/docs/metadataneed no key. - A missing or invalid key returns 401 with
error.codeinvalid_api_key. - Paused keys can be resumed without rotation. Revoked keys are permanent.
- Blank key limits inherit the account tier; they do not mean unlimited. Token and cost caps are admission checks on observed usage, not a guaranteed spending ceiling.
Errors
Errors use conventional HTTP status codes and an OpenAI-style error object with message, type, and code. Branch on code; messages are for people and may change.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_json | Bad Request |
| 401 | invalid_api_key | Missing, invalid, inactive, or expired VIABLE API key |
| 403 | invalid_origin | Access denied |
| 405 | method_not_allowed | Method Not Allowed |
| 410 | model_retired | The requested model is no longer supported by VIABLE Lab |
| 413 | request_body_too_large | Serialized model JSON bodies are limited to 32 MiB, other non-multipart bodies to 12 MiB, and multipart bodies to 26 MiB including framing |
| 429 | rate_limit_exceeded | Too Many Requests |
| 500 | internal_error | Internal Server Error |
| 501 | not_implemented | Not Implemented |
| 502 | model_provider_error | Upstream model service error |
Limits and retries
Limits protect shared capacity and your budget. Retry carefully: a slow or dropped response does not mean the request failed.
- Rate limits. Each key has a requests-per-minute limit, set per key or inherited from the account tier. A 429 includes
Retry-AfterandX-RateLimit-*headers; wait before retrying. - Body size. Model JSON requests accept up to 32 MiB including base64, other JSON 12 MiB, multipart uploads 26 MiB. Larger bodies return 413
request_body_too_largewitherror.max_bytes, before any provider call. - Streaming. Only
/v1/chat/completionsstreams. VETTING and Agent runs return complete responses. - Retries. A timeout does not prove a request failed. Check Request Logs before retrying, and reuse the same
Idempotency-Keyfor Agent jobs.
Research logging
Every authenticated request is recorded the moment it arrives and updated when it finishes, so studies, pilots, and production traffic can be filtered, replayed, and exported. A few optional headers make that data easy to slice later.
Tagging headers
| Header | Use it for |
|---|---|
X-Platform-ID | The product, course, or study sending the request. Without it, the request's origin host is used, or direct. |
X-Platform-Name | Optional readable name shown next to the platform ID. |
X-Operation-Type | The feature or step, such as generate-hint or grade-answer. Filterable in Request Logs. |
X-Request-ID | Your own correlation ID, for example a session and turn. Searchable as the client request ID. |
What is recorded
- Lifecycle. Each request appears as processing on arrival, then completed, cancelled (client disconnected), or unknown when no completion was recorded.
- Measures. Endpoint, model, API key, project, status, latency, input and output tokens, and estimated cost.
- Content. Request and response text, including prompts and VETTING configuration, kept by default for replay and export. Media sent to Agents and transcription audio are archived privately and referenced, not inlined.
- Never stored. Authorization headers, API keys, tokens, and passwords are always redacted. See the privacy policy.
Analyze
- Filter Request Logs by key, project, platform, operation, model, status, and time, then export complete CSV or JSONL files.
- Agent runs record every internal model call by step, linked to the run trace.
- From a terminal or a coding agent,
viable developer logs statsandlogs exportanswer the same questions without opening the dashboard.
Bring your own provider key
/v1/chat/completionsUse your own OpenAI, Google Gemini, UF NaviGator, OpenRouter, or other OpenAI-compatible account. Requests on a provider key are billed by that provider instead of VIABLE credits; your VIABLE API key, base URL, logs, VETTING, and Agents stay the same.
When to useYou already have a provider account, grant, or negotiated contract, your credits ran out, or you need a model only your own endpoint serves.
- Add keys under Account → Provider keys. A personal provider key applies to the projects you own, like credits (all of them by default, including new ones). A shared one belongs to a single project: its owner, and editors when the owner allows it, manage it.
- One provider key can hold several keys, for example a grant’s ten one-time keys. Each request uses the next available key, so long runs continue as keys run out, hit rate limits, or are revoked.
- Catalog models such as
gpt-6-lunabill a provider key marked "use for catalog models" with no code change; a project’s own provider key wins over the owner’s. "model": "@openrouter/anthropic/claude-sonnet-4.5"calls any model on a provider key; OpenAI-compatible endpoints receive the body unchanged exceptmodel.- Responses carry
X-Viable-Billing: credits | byok | mixedandX-Viable-Provider-Key. API-keyallowedModelsaccepts@<key-name>/*.
Behavior
- Each key has a credit type: pay as you go, one-time credit, or monthly credit with a reset day, each with an optional limit. Used-up, rate-limited and rejected keys are skipped and tried again on their own; nothing is removed automatically.
- Provider-key calls never draw credits unless the provider key is set to use VIABLE credits when its keys run out. API-key monthly cost limits count their estimated spend.
- Request logs record who paid, the key that answered, keys skipped on the way, and a cost source: the provider’s reported amount (OpenRouter), the list price, prices set on the key, or unknown.
- Errors say what to do: 402
provider_keys_exhaustedorprovider_insufficient_funds, 429provider_rate_limited(with Retry-After) orprovider_keys_rotating(retry now), 424provider_key_rejected, 502provider_endpoint_unreachable; routing errors are 400provider_key_not_foundand 409provider_key_paused. - Keys are write-only and bound to their base URL; changing the URL requires the keys again. VIABLE still logs these requests the same way as every other request.
Create a chat completion
model:requestOpenAI-compatible chat across OpenAI, Google, and VIABLE Lab models with one request shape. Set model to a catalog ID or a VIABLE alias; routing, provider translation, and logging happen behind it.
When to useGeneration, analysis, tutoring, classification, and coding tasks that need one model response. Streaming is available here and nowhere else.
Request body application/json
messagesobject[]RequiredA nonempty list of conversation messages. Legacy system-only requests are supported: Gemini translation preserves system instructions and adds a minimal user turn only when no conversation content remains after conversion. Ordinary user/assistant turns are unchanged.
2 child attributes
rolestringRequiredThe role of the message author.
One of
userassistantsystemcontentstring | object[]Required
modelstringFull model ID or VIABLE alias to use. Defaults to gpt-6-luna. Examples: gpt-6-astra, gpt-6-luna, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gemini-3.8-flash, gemini-3.1-pro-preview, gemini-2.5-flash, gpt-oss-120b, mistral-small-3.1, llama-3.3-70b-instruct, viable-1. Legacy short aliases remain accepted for existing integrations; new integrations should use full model IDs or VIABLE aliases. Prefix a model with
@<provider-key>/to call it with one of the paying account's provider keys (BYOK), for example@openrouter/anthropic/claude-sonnet-4.5or@lab-openai/gpt-6-luna. The rest of the ID is sent to that provider unchanged. Catalog models also use a provider key automatically when the project owner marked one "use for catalog models" for that provider.max_tokensintegerMaximum number of tokens to generate, passed to the provider unchanged; values above the model's max_output_tokens are rejected by the provider. For Gemini, this limit covers both thinking and final output; omitted reasoning_effort preserves provider default thinking.
max_completion_tokensintegerMaximum completion tokens for compatible reasoning models. The gateway maps max_tokens to this field for GPT-5 family models when needed. For Gemini, this limit includes thinking and final output and takes precedence over max_tokens; omitted reasoning_effort preserves provider default thinking.
reasoning_effortstringReasoning effort for compatible models. Check parameter_support.reasoning_effort_values in /v1/models or /v1/docs/metadata before sending a value.
One of
noneminimallowmediumhighxhighmaxtemperaturenumberDefault1top_pnumberDefault1nintegerDefault1How many chat completion choices to generate. (Passed through)
stopstring | string[]Up to 4 sequences where the API will stop generating. (Passed through)
presence_penaltynumberDefault0frequency_penaltynumberDefault0response_formatobjectAn object specifying the format that the model must output. Use { "type": "json_object" } to enable JSON mode, which ensures the message the model generates is valid JSON. Note: The model needs instructions in the prompt to produce JSON. Gemini maps json_object to native JSON output for ordinary and streaming chat; omitted or text format keeps the provider default.
streambooleanDefaultfalseIf true, returns OpenAI-style server-sent events from /v1/chat/completions. Streaming is supported on the main chat endpoint; vetting endpoints remain non-streaming.
providerstringOptional provider hint for public providers. Most clients should omit this and rely on model-first routing.
One of
openaigeminiuserstringYour identifier for the end user. VIABLE keeps it as a learner of the key's project (the project's logs can be filtered by learner_id). Providers that accept it receive it as sent, so use a pseudonymous id, not a name or email.
Returns 200 · application/json
Behavior
- Omit
providerfor ordinary calls; the API resolves it from the model registry. - Check
parameter_supportin/v1/modelsbefore sendingreasoning_effortor sampling parameters; support varies by model. - Streaming starts before logging finishes; logging completes asynchronously after the response.
- If the client disconnects before the call reaches the provider, nothing is sent and nothing is billed. A call already sent is not abandoned: it finishes, and Request Logs record its 499 with the provider’s actual usage and cost.
- Retired model IDs return 410
model_retired. Requests are never silently redirected to another model.
Send images, files, and PDFs
/v1/chat/completionsScope model:requestUse OpenAI-style content arrays to give a model images, files, or PDFs. Provider adapters convert supported parts, including to Gemini inline data.
When to useAsk a model to read a worksheet photo, summarize a PDF, or reason over a small file. Choose a model whose input modalities include image, file, or pdf.
type: "text"withtexttype: "image_url"withimage_url.url, an HTTPS URL or data URItype: "file"or"input_file"withfile.file_datafile.file_datatakes a base64 data URI such asdata:application/pdf;base64,...
Behavior
- Gemini file and PDF input requires base64 data, preferably as a data URI.
- Base64 data URIs are the most portable choice across providers.
- Model JSON bodies may be up to 32 MiB including base64 expansion. Larger files belong in your own upload pipeline.
Create a vetted chat completion
model:requestThe chat request you already have, plus a verification pass. A model drafts the reply, a verifier checks it against your hidden guidance, and failed drafts are revised with the verifier feedback until one passes or maxAttempts (default 3) is reached.
When to useTutoring and feedback products where a reply must follow pedagogy, for example guiding a student without giving away the answer. No answer-key setup required.
Request body application/json
messagesobject[]RequiredConversation history. System message can be included here. A system message whose string content starts with enc:: (the
valuereturned by /v1/utils/encrypt) is decrypted by the Worker before use and before logging; prompts encrypted before the dedicated prompt key was introduced keep working.2 child attributes
rolestringRequiredThe role of the message author.
One of
userassistantsystemcontentstring | object[]Required
modelstringFull chat model ID or VIABLE alias. Defaults apply if omitted. See /v1/models for supported chat models.
max_tokensintegerMaximum number of tokens to generate, passed to the provider unchanged; values above the model's max_output_tokens are rejected by the provider. For Gemini, this limit covers both thinking and final output; omitted reasoning_effort preserves provider default thinking.
max_completion_tokensintegerMaximum completion tokens for compatible reasoning models. The gateway maps max_tokens to this field for GPT-5 family models when needed. For Gemini, this limit includes thinking and final output and takes precedence over max_tokens; omitted reasoning_effort preserves provider default thinking.
reasoning_effortstringReasoning effort for compatible models. Check parameter_support.reasoning_effort_values in /v1/models or /v1/docs/metadata before sending a value.
One of
noneminimallowmediumhighxhighmaxtemperaturenumberDefault1top_pnumberDefault1nintegerDefault1How many chat completion choices to generate. (Passed through)
stopstring | string[]Up to 4 sequences where the API will stop generating. (Passed through)
presence_penaltynumberDefault0frequency_penaltynumberDefault0response_formatobjectAn object specifying the format that the model must output. Use { "type": "json_object" } to enable JSON mode, which ensures the message the model generates is valid JSON. Note: The model needs instructions in the prompt to produce JSON. Gemini maps json_object to native JSON output for ordinary and streaming chat; omitted or text format keeps the provider default.
streambooleanDefaultfalseIf true, returns OpenAI-style server-sent events from /v1/chat/completions. Streaming is supported on the main chat endpoint; vetting endpoints remain non-streaming.
providerstringOptional provider hint for public providers. Most clients should omit this and rely on model-first routing.
One of
openaigeminiuserstringYour identifier for the end user. VIABLE keeps it as a learner of the key's project (the project's logs can be filtered by learner_id). Providers that accept it receive it as sent, so use a pseudonymous id, not a name or email.
vettingConfigobject2 child attributes
systemPromptstringA specific guideline for the internal verification model to check against. If omitted, a default safety/guidance prompt is used. Values prefixed with enc:: (from /v1/utils/encrypt
value) are decrypted by the Worker before use and before logging; prompts encrypted before the dedicated prompt key was introduced keep working.maxAttemptsintegerDefault3Optional integer from 1 to 10. Defaults to 3 when omitted; invalid values return HTTP 400.
Returns 200 · application/json
Behavior
- Non-streaming by design: verification needs the complete draft.
- The response keeps the chat.completion shape and adds
vetting_infowithattemptCount,stopReason, andlastFailureReason. CheckstopReason:MAX_ATTEMPTS_REACHEDreturns the last draft, which did not pass. - Works with any chat model in the registry: OpenAI, Google, or VIABLE Lab.
vettingConfig.systemPromptand system messages acceptenc::values from the Encryption Tool, so browser-facing products do not ship guidance as plain text.
Run a full VETTING verification
model:requestStructured verification against questions and answer keys, with separate chat and verifier models and an auditable record of every attempt.
When to useAssessment and feedback workflows that must not leak the answer key, need per-attempt evidence for review, or compare models for research.
Request body application/json
modestringRequiredOperation mode: 'vetting' enables the verification layer, 'chat' provides standard chat.
One of
vettingchatconfigobjectRequired3 child attributes
maxVerificationAttemptsintegerDefault3Optional integer from 1 to 10. Defaults to 3 when omitted; invalid values return HTTP 400.
chatModelobjectRequired4 child attributes
modelIdstringRequiredFull chat model ID or VIABLE alias. Examples: viable-1, gpt-6-luna, gemini-3.8-flash, gemini-3.1-pro-preview, gemini-2.5-flash, gpt-oss-120b, mistral-small-3.1, llama-3.3-70b-instruct. See /v1/models for the current catalog. Non-chat models are rejected on VETTING endpoints. Prefix a model with
@<provider-key>/to call it with one of the paying account's provider keys (BYOK), for example@openrouter/anthropic/claude-sonnet-4.5or@lab-openai/gpt-6-luna. The rest of the ID is sent to that provider unchanged. Catalog models also use a provider key automatically when the project owner marked one "use for catalog models" for that provider.temperaturenumberSampling temperature. If omitted, server defaults apply (e.g., Chat: 0.7, Verify: 0.1).
maxTokensintegerMaximum tokens to generate. If omitted, server defaults apply (e.g., Chat: 1024, Verify: 512).
topPnumberNucleus sampling parameter.
verificationModelobject4 child attributes
modelIdstringRequiredFull chat model ID or VIABLE alias. Examples: viable-1, gpt-6-luna, gemini-3.8-flash, gemini-3.1-pro-preview, gemini-2.5-flash, gpt-oss-120b, mistral-small-3.1, llama-3.3-70b-instruct. See /v1/models for the current catalog. Non-chat models are rejected on VETTING endpoints. Prefix a model with
@<provider-key>/to call it with one of the paying account's provider keys (BYOK), for example@openrouter/anthropic/claude-sonnet-4.5or@lab-openai/gpt-6-luna. The rest of the ID is sent to that provider unchanged. Catalog models also use a provider key automatically when the project owner marked one "use for catalog models" for that provider.temperaturenumberSampling temperature. If omitted, server defaults apply (e.g., Chat: 0.7, Verify: 0.1).
maxTokensintegerMaximum tokens to generate. If omitted, server defaults apply (e.g., Chat: 1024, Verify: 512).
topPnumberNucleus sampling parameter.
promptsobjectRequiredContainer for system prompts. The object itself is required in the request, but the fields within are optional.
2 child attributes
chatSystemPromptstringBase system prompt for the chat model. If omitted, a default helpful tutor prompt is used. Values prefixed with enc:: (from /v1/utils/encrypt
value) are decrypted by the Worker before use and before logging; prompts encrypted before the dedicated prompt key was introduced keep working.verificationSystemPromptstringBase system prompt for the verification model. If omitted and mode='vetting', a default evaluator prompt is constructed using the chat prompt and context. Values prefixed with enc:: (from /v1/utils/encrypt
value) are decrypted by the Worker before use and before logging; prompts encrypted before the dedicated prompt key was introduced keep working.
contextobjectContextual information. The 'items' array is required if mode='vetting'.
3 child attributes
sessionIdstringSession identifier.
userIdstringUser identifier.
itemsobject[]Required(Required if mode='vetting') Array of questions and their corresponding answer keys.
2 child attributes
questionobjectRequiredanswerKeyobjectRequired
messagesobject[]RequiredConversation history, ending with the last user message. Client should provide messages with roles 'user' and optionally previous 'assistant' messages. Do not include 'system' role messages here; use the
promptsfield instead.2 child attributes
rolestringRequiredThe role of the message author.
One of
userassistantsystemcontentstring | object[]Required
Returns 200 · application/json
Behavior
- The chat model and the verification model can come from different providers.
verification.attemptsrecords each draft, whether it passed, and the verifier output.usageseparates chat and verification tokens so you can see what verification costs.prompts.chatSystemPromptandprompts.verificationSystemPromptacceptenc::values.- Use VETTING chat instead when you do not need answer keys or per-attempt detail.
Run a published Agent
agents:executeCall a workflow you built and published in the dashboard. One request can chain models, branch, loop within budgets, generate images, and return text plus assets, behind a stable URL.
When to useOne product turn needs orchestration: classify then respond, decide whether to draw, or run several model steps. If a single model call is enough, use chat completions instead.
Path and header parameters
slugstringpathRequiredPublished agent slug.
Idempotency-KeystringheaderOptional stable key for the same logical request. Completed requests replay; mismatched payloads and pending requests return 409. Never change the key to retry an uncertain outcome.
Request body application/json
inputstring | object | object[]Latest user turn for an Agent. Text is retained exactly. Inline image/file data is bounded, archived byte-for-byte in private R2, and represented in research logs by an authenticated asset reference. HTTPS URLs are retained exactly as restricted provenance and are not fetched by VIABLE Lab.
conversation_idstringContinue an existing agent conversation. Omit on the first turn, store the returned value, and resend it for later turns from the same end-user session.
messagestringBackward-compatible text-only alias for input. Omit input when using this field.
messagesobject[]With history_mode=client, preserves supported system/developer/user/assistant messages in order; final message must be user. Tools are not supported. Without an explicit mode, legacy last-user extraction remains unchanged.
2 child attributes
rolestringRequiredThe role of the message author.
One of
userassistantsystemcontentstring | object[]Required
history_modestringDefault"server"server preserves legacy conversation behavior: steps get the conversation's latest messages, the oldest left out only when a model's context window is full (the step output then has context_trimmed: true); an agent whose Input step sets config.history to "summarize" folds the oldest turns into a running summary stored with the conversation once its history nears the models' context budget. client uses the supplied complete messages transcript; none is a single turn without a conversation. Both client and none retain research logs and usage, and reject conversation_id.
One of
serverclientnonecontextobjectOptional current request data, max 128 KiB and nesting depth 16. Nodes explicitly opt in with includeContext or context templates. Never automatically appended to conversation history; research traces retain snapshots.
userstringOptional opaque identifier for the end user, such as your app's student ID. Memory steps keep long-term facts per agent and user; without it, memory follows the server conversation. Each turn is kept with the learner it names in the project's Learners (their id is shown with them, so their data joins back to your records). Do not send names or emails.
sourcesstringHow to list the reply's cited sources in its text: none (default here; read them from output.citations), markdown (a numbered Sources list with links, [n] markers linked), or text ("Sources:" and one line per source with its link).
One of
nonemarkdowntextstreambooleanDefaultfalsetrue streams the run as server-sent events, so callers can show what it is doing while it works.
Returns 200 · application/json
Behavior
- Store the returned
conversation_idper end-user thread and agent; resend it to continue. It is bound to the key or user that created it. history_mode: "client"sends the fullmessagestranscript instead;"none"runs one independent turn. Neither acceptsconversation_id.contextis current-request JSON for steps that opt in. It is retained in research traces, not added to future chat history.- Send
user(an opaque ID such as your student ID, at most 128 characters) when the agent remembers people across conversations; memory steps keep facts per agent and user. - When an Ask user step ends the turn,
output.awaiting_inputis true and the reply is a question; send the answer with the sameconversation_idto continue. - For image input, send
input_textplusinput_imageparts; the receiving LLM step needs an image-capable model. - Inline images are limited to 8 MiB each and 10 MiB per request. Exact bytes are archived privately; logs and traces keep authenticated asset references, not base64.
run_ididentifies one execution. Request logs show the run and every internal model call, tagged by agent, run, and step.- The same graph is also callable as
POST /v1/agents/{slug}/chat/completions, or as a background job at/v1/agents/{slug}/jobswith anIdempotency-Key.
Integration formats
Use this agent in your app
All supported endpoints are available after publishing. The selection below changes the example only, not the agent.
https://api.viablelab.org/v1/agents/my-agent/runsUse a server-side API key with agents:execute. Never include it in browser code.
Send the latest message as input. Save the returned conversation_id per user and thread; include it on subsequent turns.
When the agent answers from a knowledge base, output.citations lists the sources its reply cites as [1], [2]… (one per page or file, with title, link and snippet), so your app can show where an answer comes from. To have them listed in output.text instead, send sources: "markdown" or "text".
If the Reply step returns result data, read it as output.data: what the run decided or found (a label, a score, a handoff and its reasons) for your app or system to act on. It is never part of the reply text.
Send user with a stable ID for the person in your app (up to 128 characters; avoid emails or names). Memory steps remember facts per user across conversations; without it, memory lasts only for one conversation.
/runs keeps this HTTP request open until the reply is ready. Read output.text. Start here for ordinary request/response integration; no polling is needed.
curl 'https://api.viablelab.org/v1/agents/my-agent/runs' \
-H "Authorization: Bearer $VIABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "Hello!",
"history_mode": "server",
"user": "student-042"
}'Show progress and stream the reply
Add "stream": true to get server-sent events while the agent works: run.progress with a stage (thinking, listening, searching, reading, checking, creating, writing, speaking) as the kind of work changes, run.delta with the reply as it is written, then run.completed with the same body as without streaming. Stages never name the agent’s steps, so you can show them to your users.
The reply streams when it goes straight from a model step to the Reply step. A reply checked first (by a VETTING step, say) arrives whole in run.completed, after the stages. Without stream, nothing changes: existing integrations keep working as they are.
const response = await fetch("https://api.viablelab.org/v1/agents/my-agent/runs", {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.VIABLE_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ input: 'Hello!', user: 'student-042', stream: true })
})
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
let buffer = ''
for (;;) {
const { value, done } = await reader.read()
if (done) break
buffer += value
const events = buffer.split('\n\n')
buffer = events.pop()
for (const block of events) {
const name = block.match(/^event: (.+)$/m)?.[1]
const data = JSON.parse(block.match(/^data: (.+)$/m)?.[1] ?? 'null')
if (name === 'run.progress') showStage(data.stage) // thinking, searching, reading, checking, creating, writing
if (name === 'run.delta') appendToReply(data.text) // the reply as it is written, when it streams
if (name === 'run.completed') showReply(data.output) // final: replaces what streamed
if (name === 'run.failed') showError(data.error)
}
}Next turn & reference data
{
"input": "Tell me more.",
"conversation_id": "agtc_REPLACE_WITH_RETURNED_ID",
"user": "student-042",
"context": {
"current_document": "The latest version only"
}
}When the workflow asks the user something (an Ask user step), the reply is that question and output.awaiting_input is true. Send the user’s answer as the next turn with the same conversation_id; the workflow continues after the question.
Use history_mode: "none" for independent requests. Use history_mode: "client" with messages to manage the full history yourself; omit conversation_id.
context accepts your own JSON fields. Enable Use current reference data on each LLM step that needs those fields, or reference them explicitly in its prompt. Send the latest snapshot each time; it is retained in research traces, not automatically added to future chat history. This does not remove older material already stored in a conversation.
Retries, limits and safe integration
Keep API keys on your server. A shared key's rate limit is shared by all its users. Blank key limits inherit the account tier; they do not mean unlimited.
For each logical turn, generate an Idempotency-Key (8–128 letters, digits, dots, underscores, colons or hyphens). Reuse it with the same body after a lost response. Never create a new key to bypass an uncertain pending request.
429 is a rejection with bounded provider retries, not a promise of background completion. Respect Retry-After. A 409 pending response requires retrying the original key or checking logs; a timeout does not prove the provider did no work.
How background jobs and queues work
Background jobs are an optional delivery mode for Native Agent, not a third conversation format. View the background job example above for its URL and code. Your server submits → the platform queues and runs the workflow → your server retrieves the result.
A 202 response includes id and status_url. GET that URL with the same API key every few seconds, respecting Retry-After. A completed job includes the native response in result.
States: queued → running → completed, failed, or recovery_required. Queue waiting expires after 15 minutes; execution still has a separate deadline. Keep one turn outstanding per conversation. Recovery-required means the result is uncertain—do not automatically submit the workflow again.
The platform manages the queue; application developers do not need a Cloudflare account or queue credentials. Neither synchronous endpoint automatically switches to it. It does not bypass key quotas or turn every provider 429 into an eventual success. A 503 before admission means the capability is unavailable; do not switch execution routes after an uncertain response.
Create embeddings
model:requestOpenAI-compatible vectors for retrieval, semantic search, clustering, and reranking.
When to useIndex course material, match questions to resources, or build retrieval for a tutor.
Request body application/json
modelstringDefault"nomic-embed-text-v1.5"Prefix a model with
@<provider-key>/to call it with one of the paying account's provider keys (BYOK), for example@openrouter/anthropic/claude-sonnet-4.5or@lab-openai/gpt-6-luna. The rest of the ID is sent to that provider unchanged. Catalog models also use a provider key automatically when the project owner marked one "use for catalog models" for that provider.inputstring | string[]Requiredencoding_formatstringDefault"float"One of
floatbase64dimensionsintegerOptional dimensions if supported by the selected model.
Returns 200 · application/json
Behavior
- The default model is
nomic-embed-text-v1.5, hosted by VIABLE Lab. - Cost is logged when provider usage and registry pricing are available.
Generate images
model:requestOpenAI Images-style generation across FLUX, GPT Image, and Gemini image models.
When to useIllustrations, diagrams, and visual prompts for learning materials.
Request body application/json
modelstringDefault"flux.2-klein"Use an image_generation model from GET /v1/models. Supports flux.2-klein, Gemini 3.1 Flash/Lite Image, and GPT Image models; chat models cannot be used here.
promptstringRequirednintegerDefault1Integer 1–10 for Flux and GPT Image; Gemini accepts only 1. No automatic fan-out or retries.
sizestringGPT Image: provider-supported WIDTHxHEIGHT or auto. Gemini: 0.5K, 1K, 2K, 4K (Lite: 1K only), or square pixel equivalents. The 1024x1536 and 1536x1024 presets request 1K at 2:3 and 3:2, not exact pixel dimensions. See parameter_support.size_values in the catalog.
qualitystringGPT Image: auto/low/medium/high; GPT Image 2.5 also accepts xhigh/max. Gemini does not accept quality. Flux uses provider-supported values.
response_formatstringDefault"url"urlreturns an API-hosted HTTPS asset URL.b64_jsonreturns inline base64 to the caller; logs store only a redacted size summary plus an asset preview URL when asset storage is available.One of
urlb64_jsonaspect_ratiostringGemini only. See parameter_support.aspect_ratio_values. Defaults to 1:1; cannot be combined with portrait/landscape pixel presets. Use a resolution tier with this field.
output_formatstringGPT Image and Flux provider output format. Gemini returns its native image MIME type and does not accept this field.
One of
pngjpegwebpstreambooleanOnly non-streaming generation is supported. partial_images is rejected.
One of
false
Returns 200 · application/json
Behavior
urlresponses are API-hosted HTTPS assets;b64_jsonreturns inline data and logs keep only a redacted summary.- Parameter support differs by model family. Read
parameter_supportin/v1/modelsfor sizes, quality, and aspect ratios. - GPT Image and Gemini costs come from reported token usage; FLUX uses a per-image estimate.
Speech and transcription
model:requestTurn text into speech, or transcribe uploaded audio, with OpenAI-compatible request shapes.
When to useRead-aloud support, spoken feedback, and transcribing recorded explanations or interviews.
POST /v1/audio/speech
Request body application/json
inputstringRequiredThe text to generate audio for. Maximum length: 4096 characters.
modelstringDefault"kokoro"Speech model. Defaults to kokoro. Retired IDs return 410 model_retired without redirection.
One of
kokorovoicestringKokoro voice ID; defaults to af_heart. Read voice_values from /v1/models for all 54 choices.
instructionsstringKokoro does not support instructions; non-empty values return 400.
response_formatstringDefault"mp3"The audio output format. Inspect parameter_support.response_format_values because formats vary by model.
One of
mp3opusflacwavpcmspeednumberDefault1The speed of the generated audio (0.25 to 4.0).
Returns 200 · audio/mpeg
Binary content. The format follows the request's response_format.
POST /v1/audio/transcriptions
Request body multipart/form-data
filefileRequiredAudio file upload, bounded by the 26 MiB multipart request limit. For attributed requests the exact bytes are archived as a private research asset.
modelstringDefault"whisper-large-v3"Transcription model. Defaults to whisper-large-v3. Retired IDs return 410 model_retired.
One of
whisper-large-v3gpt-transcribelanguagestringOptional ISO-639-1 code for whisper-large-v3. For gpt-transcribe use languages[]; singular language returns 400 invalid_transcription_language.
promptstringresponse_formatstringDefault"json"gpt-transcribe supports non-streaming json through this gateway; other formats or stream=true return 400 unsupported_transcription_format. whisper-large-v3 supports every listed format.
One of
jsontextsrtverbose_jsonvtttemperaturenumberduration_secondsnumberOptional client duration for cost estimates when provider duration is unavailable. Provider duration takes precedence.
duration_minutesnumberOptional client duration for cost estimates when provider duration is unavailable. Provider duration takes precedence.
languages[]string[]Optional language hints for gpt-transcribe. Repeat languages[] multipart fields for multiple ISO-639-1 codes.
keywords[]string[]Optional keyword hints for gpt-transcribe. Repeat keywords[] multipart fields. Each keyword must be one line without angle brackets.
streambooleanDefaultfalseKeep false or omit. gpt-transcribe stream=true is unsupported by this gateway and returns 400.
Returns 200 · application/json
Behavior
- Speech defaults to
kokorowith 54 voices;af_heartis the default voice. - Transcription uses multipart/form-data, not JSON.
whisper-large-v3is the default model and returnsjson(default),text,srt,verbose_jsonorvtt. - Audio is priced per minute of the duration the model reports; the JSON transcript includes it as
usage.seconds.duration_secondsis only a fallback when no duration is reported.
Encrypt and decrypt prompt values
utils:cryptoEncrypt prompt text for VETTING fields that accept enc:: values, so guidance is not shipped to a browser as plain text. Prefix the returned encryptedText with enc:: before using it.
When to useA browser-facing VETTING integration has to carry system guidance in client code. Calling the API from your own backend keeps guidance off the client entirely and is the stronger option.
POST /v1/utils/encrypt
Request body application/json
textstringRequiredMust contain non-whitespace characters.
Returns 200 · application/json
POST /v1/utils/decrypt
Request body application/json
encryptedTextstringRequiredMust contain non-whitespace characters. A leading enc:: prefix is accepted.
Returns 200 · application/json
Behavior
- Automatic
enc::handling applies to documented VETTING prompt fields only, not every endpoint. - Supported
enc::fields are decrypted before logging, so research exports contain the prompt the model actually used. - The dashboard Encryption Tool is a convenience UI over these endpoints.
List models
The live catalog: IDs, modes, providers, modalities, context windows, parameter support, and pricing notes. No key required.
When to usePopulate a model picker, check which parameters a model accepts, or keep pricing in sync without hardcoding.
Returns 200 · application/json
Behavior
GET /v1/docs/metadatareturns the catalog plus endpoints and quickstarts in one compact payload for portals and agents.- VIABLE aliases (
viable-1,viable-2,viable-3) are stable names whose underlying models are managed by VIABLE Lab.
Machine-readable files
For code generators, CI checks, coding agents, and AI search. All public and requiring no key.
