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_url is https://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.

Message for your agent
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_KEY on every protected request. X-API-Key is also accepted.
  • Scopes: model:request for model endpoints, agents:execute for published Agents, utils:crypto for encryption utilities.
  • Optional limits: requests per minute, daily token cap, monthly cost cap, and an expiration date.
  • allowedModels restricts a key to exact model IDs or aliases.
  • Public GET endpoints such as /v1/models and /v1/docs/metadata need no key.
  • A missing or invalid key returns 401 with error.code invalid_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.

StatusCodeMeaning
400invalid_jsonBad Request
401invalid_api_keyMissing, invalid, inactive, or expired VIABLE API key
403invalid_originAccess denied
405method_not_allowedMethod Not Allowed
410model_retiredThe requested model is no longer supported by VIABLE Lab
413request_body_too_largeSerialized model JSON bodies are limited to 32 MiB, other non-multipart bodies to 12 MiB, and multipart bodies to 26 MiB including framing
429rate_limit_exceededToo Many Requests
500internal_errorInternal Server Error
501not_implementedNot Implemented
502model_provider_errorUpstream 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-After and X-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_large with error.max_bytes, before any provider call.
  • Streaming. Only /v1/chat/completions streams. 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-Key for 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

HeaderUse it for
X-Platform-IDThe product, course, or study sending the request. Without it, the request's origin host is used, or direct.
X-Platform-NameOptional readable name shown next to the platform ID.
X-Operation-TypeThe feature or step, such as generate-hint or grade-answer. Filterable in Request Logs.
X-Request-IDYour 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 stats and logs export answer the same questions without opening the dashboard.

Bring your own provider key

POST/v1/chat/completions

Use 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-luna bill 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 except model.
  • Responses carry X-Viable-Billing: credits | byok | mixed and X-Viable-Provider-Key. API-key allowedModels accepts @<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_exhausted or provider_insufficient_funds, 429 provider_rate_limited (with Retry-After) or provider_keys_rotating (retry now), 424 provider_key_rejected, 502 provider_endpoint_unreachable; routing errors are 400 provider_key_not_found and 409 provider_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

Scope model:request

OpenAI-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[]Required

    A 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
    • rolestringRequired

      The role of the message author.

      One of userassistantsystem

    • contentstring | object[]Required
  • modelstring

    Full 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.5 or @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_tokensinteger

    Maximum 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_tokensinteger

    Maximum 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_effortstring

    Reasoning effort for compatible models. Check parameter_support.reasoning_effort_values in /v1/models or /v1/docs/metadata before sending a value.

    One of noneminimallowmediumhighxhighmax

  • temperaturenumberDefault 1
  • top_pnumberDefault 1
  • nintegerDefault 1

    How many chat completion choices to generate. (Passed through)

  • stopstring | string[]

    Up to 4 sequences where the API will stop generating. (Passed through)

  • presence_penaltynumberDefault 0
  • frequency_penaltynumberDefault 0
  • response_formatobject

    An 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.

  • streambooleanDefault false

    If true, returns OpenAI-style server-sent events from /v1/chat/completions. Streaming is supported on the main chat endpoint; vetting endpoints remain non-streaming.

  • providerstring

    Optional provider hint for public providers. Most clients should omit this and rely on model-first routing.

    One of openaigemini

  • userstring

    Your 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 provider for ordinary calls; the API resolves it from the model registry.
  • Check parameter_support in /v1/models before sending reasoning_effort or 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

POST/v1/chat/completionsScope model:request

Use 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" with text
  • type: "image_url" with image_url.url, an HTTPS URL or data URI
  • type: "file" or "input_file" with file.file_data
  • file.file_data takes a base64 data URI such as data: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

Scope model:request

The 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[]Required

    Conversation history. System message can be included here. A system message whose string content starts with enc:: (the value returned 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
    • rolestringRequired

      The role of the message author.

      One of userassistantsystem

    • contentstring | object[]Required
  • modelstring

    Full chat model ID or VIABLE alias. Defaults apply if omitted. See /v1/models for supported chat models.

  • max_tokensinteger

    Maximum 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_tokensinteger

    Maximum 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_effortstring

    Reasoning effort for compatible models. Check parameter_support.reasoning_effort_values in /v1/models or /v1/docs/metadata before sending a value.

    One of noneminimallowmediumhighxhighmax

  • temperaturenumberDefault 1
  • top_pnumberDefault 1
  • nintegerDefault 1

    How many chat completion choices to generate. (Passed through)

  • stopstring | string[]

    Up to 4 sequences where the API will stop generating. (Passed through)

  • presence_penaltynumberDefault 0
  • frequency_penaltynumberDefault 0
  • response_formatobject

    An 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.

  • streambooleanDefault false

    If true, returns OpenAI-style server-sent events from /v1/chat/completions. Streaming is supported on the main chat endpoint; vetting endpoints remain non-streaming.

  • providerstring

    Optional provider hint for public providers. Most clients should omit this and rely on model-first routing.

    One of openaigemini

  • userstring

    Your 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.

  • vettingConfigobject
    2 child attributes
    • systemPromptstring

      A 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.

    • maxAttemptsintegerDefault 3

      Optional 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_info with attemptCount, stopReason, and lastFailureReason. Check stopReason: MAX_ATTEMPTS_REACHED returns the last draft, which did not pass.
  • Works with any chat model in the registry: OpenAI, Google, or VIABLE Lab.
  • vettingConfig.systemPrompt and system messages accept enc:: values from the Encryption Tool, so browser-facing products do not ship guidance as plain text.

Run a full VETTING verification

Scope model:request

Structured 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

  • modestringRequired

    Operation mode: 'vetting' enables the verification layer, 'chat' provides standard chat.

    One of vettingchat

  • configobjectRequired
    3 child attributes
    • maxVerificationAttemptsintegerDefault 3

      Optional integer from 1 to 10. Defaults to 3 when omitted; invalid values return HTTP 400.

    • chatModelobjectRequired
      4 child attributes
      • modelIdstringRequired

        Full 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.5 or @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.

      • temperaturenumber

        Sampling temperature. If omitted, server defaults apply (e.g., Chat: 0.7, Verify: 0.1).

      • maxTokensinteger

        Maximum tokens to generate. If omitted, server defaults apply (e.g., Chat: 1024, Verify: 512).

      • topPnumber

        Nucleus sampling parameter.

    • verificationModelobject
      4 child attributes
      • modelIdstringRequired

        Full 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.5 or @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.

      • temperaturenumber

        Sampling temperature. If omitted, server defaults apply (e.g., Chat: 0.7, Verify: 0.1).

      • maxTokensinteger

        Maximum tokens to generate. If omitted, server defaults apply (e.g., Chat: 1024, Verify: 512).

      • topPnumber

        Nucleus sampling parameter.

  • promptsobjectRequired

    Container for system prompts. The object itself is required in the request, but the fields within are optional.

    2 child attributes
    • chatSystemPromptstring

      Base 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.

    • verificationSystemPromptstring

      Base 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.

  • contextobject

    Contextual information. The 'items' array is required if mode='vetting'.

    3 child attributes
    • sessionIdstring

      Session identifier.

    • userIdstring

      User identifier.

    • itemsobject[]Required

      (Required if mode='vetting') Array of questions and their corresponding answer keys.

      2 child attributes
      • questionobjectRequired
      • answerKeyobjectRequired
  • messagesobject[]Required

    Conversation 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 prompts field instead.

    2 child attributes
    • rolestringRequired

      The role of the message author.

      One of userassistantsystem

    • contentstring | object[]Required

Returns 200 · application/json

Behavior

  • The chat model and the verification model can come from different providers.
  • verification.attempts records each draft, whether it passed, and the verifier output.
  • usage separates chat and verification tokens so you can see what verification costs.
  • prompts.chatSystemPrompt and prompts.verificationSystemPrompt accept enc:: values.
  • Use VETTING chat instead when you do not need answer keys or per-attempt detail.

Run a published Agent

Scope agents:execute

Call 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

  • slugstringpathRequired

    Published agent slug.

  • Idempotency-Keystringheader

    Optional 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_idstring

    Continue 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.

  • messagestring

    Backward-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
    • rolestringRequired

      The role of the message author.

      One of userassistantsystem

    • contentstring | 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 serverclientnone

  • contextobject

    Optional 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.

  • userstring

    Optional 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.

  • sourcesstring

    How 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 nonemarkdowntext

  • streambooleanDefault false

    true 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_id per 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 full messages transcript instead; "none" runs one independent turn. Neither accepts conversation_id.
  • context is 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_input is true and the reply is a question; send the answer with the same conversation_id to continue.
  • For image input, send input_text plus input_image parts; 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_id identifies 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}/jobs with an Idempotency-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.

POST
https://api.viablelab.org/v1/agents/my-agent/runs

Use 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.

Native Agent · first turn
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.

Native Agent · streaming (Node.js, on your server)
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
Next turn · JSON body
{
  "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.

Full reference · limits, retries & queue behavior ↗

Create embeddings

Scope model:request

OpenAI-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.5 or @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[]Required
  • encoding_formatstringDefault "float"

    One of floatbase64

  • dimensionsinteger

    Optional 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

Scope model:request

OpenAI 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.

  • promptstringRequired
  • nintegerDefault 1

    Integer 1–10 for Flux and GPT Image; Gemini accepts only 1. No automatic fan-out or retries.

  • sizestring

    GPT 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.

  • qualitystring

    GPT 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"

    url returns an API-hosted HTTPS asset URL. b64_json returns 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_json

  • aspect_ratiostring

    Gemini 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_formatstring

    GPT Image and Flux provider output format. Gemini returns its native image MIME type and does not accept this field.

    One of pngjpegwebp

  • streamboolean

    Only non-streaming generation is supported. partial_images is rejected.

    One of false

Returns 200 · application/json

Behavior

  • url responses are API-hosted HTTPS assets; b64_json returns inline data and logs keep only a redacted summary.
  • Parameter support differs by model family. Read parameter_support in /v1/models for sizes, quality, and aspect ratios.
  • GPT Image and Gemini costs come from reported token usage; FLUX uses a per-image estimate.

Speech and transcription

Scope model:request

Turn 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

  • inputstringRequired

    The 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 kokoro

  • voicestring

    Kokoro voice ID; defaults to af_heart. Read voice_values from /v1/models for all 54 choices.

  • instructionsstring

    Kokoro 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 mp3opusflacwavpcm

  • speednumberDefault 1

    The 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

  • filefileRequired

    Audio 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-transcribe

  • languagestring

    Optional ISO-639-1 code for whisper-large-v3. For gpt-transcribe use languages[]; singular language returns 400 invalid_transcription_language.

  • promptstring
  • response_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_jsonvtt

  • temperaturenumber
  • duration_secondsnumber

    Optional client duration for cost estimates when provider duration is unavailable. Provider duration takes precedence.

  • duration_minutesnumber

    Optional 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.

  • streambooleanDefault false

    Keep false or omit. gpt-transcribe stream=true is unsupported by this gateway and returns 400.

Returns 200 · application/json

Behavior

  • Speech defaults to kokoro with 54 voices; af_heart is the default voice.
  • Transcription uses multipart/form-data, not JSON. whisper-large-v3 is the default model and returns json (default), text, srt, verbose_json or vtt.
  • Audio is priced per minute of the duration the model reports; the JSON transcript includes it as usage.seconds. duration_seconds is only a fallback when no duration is reported.

Encrypt and decrypt prompt values

Scope utils:crypto

Encrypt 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

  • textstringRequired

    Must contain non-whitespace characters.

Returns 200 · application/json

POST /v1/utils/decrypt

Request body application/json

  • encryptedTextstringRequired

    Must 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/metadata returns 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.