Skip to content

LLM API ​

Generate text or function calls as JSON or an SSE stream. Completions cost credits based on token usage; model discovery and status lookups are free.

Authentication ​

Send Authorization: Bearer $VOICELAB_API_KEY. Restricted keys need {"llm":"access"} in their permission map for the complete workflow. The compatibility scopes are llm:read for models and request status, and llm:write for completions. Existing unrestricted keys can use these routes. See API-key permissions.

The developer API uses /v1. Ask's /api/v1/llm/* and /api/v1/chats/* routes require a platform JWT and remain uncharged. Developer completions use the messages you supply; they do not add Ask's prompts, knowledge, or history.

List models ​

GEThttps://api.voicelab.uz/v1/models

List configured, priced models and their effective API limits. Requires llm:read.

bash
curl -fS 'https://api.voicelab.uz/v1/models' \
  -H "Authorization: Bearer $VOICELAB_API_KEY"

The response is an object with object: "list" and a data array. Each entry contains:

FieldMeaning
idPublic model ID to send in a completion request
objectmodel
name, descriptionOptional display metadata
parameters, context_windowOptional model parameter count and context capacity
owned_byvoicelab
created0; no model creation timestamp is supplied
pricing.version, pricing.modelActive price version and public model ID
pricing.input_usd_cents_per_million_tokensInput price in USD cents per million tokens
pricing.output_usd_cents_per_million_tokensOutput price in USD cents per million tokens
pricing.credits_per_usdConversion used for this price version
limits.max_input_bytesEffective serialized messages/tools limit, currently 65536
limits.max_output_tokensEffective output-token limit, currently 4096
limits.requests_per_second_per_replicaAccount admission rate on each API replica, currently 70
limits.max_concurrent_requests_per_accountRunning requests across keys, models and replicas, currently 70
capabilitiesAdvertised features, including vision, document or tool_calling when supported
reasoning_efforts, reasoning_effort_defaultOptional model-specific effort values and default

Resolve model IDs and prices from this response. The examples below use aisha-comet; use a model returned for your account. Only configured models with active pricing appear. A model's context capacity does not override the endpoint limits.

Use each model's current capabilities and effort values. Missing capabilities do not imply support. Discovery failure or stale metadata can leave text generation available while new media, tools and explicit reasoning efforts are unavailable. Upload images and PDFs before referencing them in a completion; PDFs require both vision and document.

Create a chat completion ​

POSThttps://api.voicelab.uz/v1/chat/completions

Generate a text or function-call completion, billed in credits. Requires llm:write.

bash
curl -fS -D llm-response.headers 'https://api.voicelab.uz/v1/chat/completions' \
  -H "Authorization: Bearer $VOICELAB_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: hello-turn-001' \
  -d '{
    "model": "aisha-comet",
    "messages": [
      {"role": "system", "content": "Answer briefly."},
      {"role": "user", "content": "Salom!"}
    ],
    "max_tokens": 128
  }'

Request fields ​

FieldRequiredRules
modelyesPublic ID from GET /v1/models
messagesyes1 to 101 messages with role and text or supported text/file content parts
streamnoBoolean; defaults to false
stream_options.include_usagenoSet to true for a final SSE usage chunk
max_tokensnoInteger from 1 to 4096; defaults to 1024
max_completion_tokensnoAlias for max_tokens; send only one of these fields
temperaturenoNumber from 0 to 2; model support varies
top_pnoNumber from 0 to 1; model support varies
stopnoOne string or 1 to 4 strings, each 1 to 200 bytes
seednoInteger; model support varies
nnoOnly 1 is accepted
toolsnoUp to 32 function definitions; see function calls
tool_choicenoauto, none, required, or a supplied function selector
parallel_tool_callsnoBoolean; model support varies
thinkingnoBoolean reasoning control; send at most one reasoning control
reasoningnoObject with enabled boolean and optional effort
reasoning_effortnoOne value from the selected model's advertised reasoning_efforts

Messages support system, user, assistant, and tool roles. Content can be UTF-8 text or an array of text/file parts. Images and PDFs must first be uploaded through Files; file parts are allowed only in user messages. Audio, video, external URLs and inline data URIs are unsupported. An assistant message with function calls may have empty content. Tool messages must include tool_call_id matching a preceding assistant call.

Choose at most one of thinking, reasoning, or reasoning_effort. The reasoning.effort field accepts the same effort names as reasoning_effort. Unknown fields, provider URLs/keys, and multiple choices are rejected.

thinking: false can select a model's lowest effort rather than disable reasoning. It does not guarantee zero reasoning tokens or faster responses. Use the model's advertised effort values.

JSON response ​

Example 200 OK response with stream: false:

json
{
  "id": "chatcmpl_example",
  "object": "chat.completion",
  "created": 1788998400,
  "model": "aisha-comet",
  "choices": [
    {
      "index": 0,
      "message": {"role": "assistant", "content": "Salom!"},
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 20,
    "completion_tokens": 4,
    "total_tokens": 24
  }
}

created is Unix time in seconds. message.content can be null for a tool-only response; message.tool_calls and message.reasoning_content are optional. Finish reasons are stop, length, tool_calls, or content_filter. Usage may also contain prompt_tokens_details.cached_tokens and completion_tokens_details.reasoning_tokens.

Read X-LLM-Request-ID from the response headers for the billable request ID. It matches the completion id. X-Credits-Charged reports the whole credits debited when billing is settled. A completed answer with unverified usage can instead return usage: null and X-LLM-Billing-Status: pending, without X-Credits-Charged. Unknown usage is not zero cost. Successful LLM bodies have no generic request_id wrapper; the HTTP trace ID in error bodies is separate from the LLM request ID.

Stream a completion ​

Use the same completion endpoint with stream: true. curl -N displays SSE events as they arrive:

bash
curl -fS -N -D llm-stream.headers 'https://api.voicelab.uz/v1/chat/completions' \
  -H "Authorization: Bearer $VOICELAB_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: hello-stream-001' \
  -d '{
    "model": "aisha-comet",
    "messages": [{"role": "user", "content": "Say hello."}],
    "max_tokens": 128,
    "stream": true,
    "stream_options": {"include_usage": true}
  }'

The response uses Content-Type: text/event-stream. Events are separated by a blank line. An illustrative stream is:

text
data: {"id":"chatcmpl_example","object":"chat.completion.chunk","model":"aisha-comet","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello!"},"finish_reason":null}],"created":1788998400}

data: {"id":"chatcmpl_example","object":"chat.completion.chunk","model":"aisha-comet","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"created":1788998400}

data: {"id":"chatcmpl_example","object":"chat.completion.chunk","model":"aisha-comet","choices":[],"usage":{"prompt_tokens":10,"completion_tokens":2,"total_tokens":12},"created":1788998400}

data: [DONE]

Append choices[].delta.content as it arrives. Reasoning can arrive in delta.reasoning_content. Assemble function-call arguments from fragments using each delta.tool_calls[].index. Network reads can split or combine events. Parse SSE event boundaries.

The final usage chunk appears only with stream_options.include_usage: true. [DONE] is sent after a complete answer and either credit settlement or durable recording of unresolved usage. A usage footer can contain usage: null when counts are unverified. An HTTP 200 alone does not prove a stream completed: it can end with an llm_request_incomplete error event and no [DONE], or disconnect. Read the error inside the SSE data, retain X-LLM-Request-ID, and check request status before retrying. Streaming responses do not provide X-Credits-Charged; use request status for the settled charge.

Treat any SSE error or EOF without [DONE] as incomplete. Never execute partial tool-call arguments. Analytics record streaming failures separately from the HTTP status, which cannot change once the stream starts.

Function calls ​

Supply functions as data. Your application validates and executes the selected function; VoiceLab does not run it. This completion request asks the model to call one function:

json
{
  "model": "aisha-comet",
  "messages": [{"role": "user", "content": "What is the weather in Tashkent?"}],
  "max_tokens": 256,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get current weather for a city.",
        "parameters": {
          "type": "object",
          "properties": {"city": {"type": "string"}},
          "required": ["city"]
        }
      }
    }
  ],
  "tool_choice": {"type": "function", "function": {"name": "get_weather"}}
}

Function names must be unique and match [a-zA-Z0-9_-]{1,64}. Each function's parameters must be a JSON object schema. tool_choice: "required" requires at least one tool; a named selector must refer to a supplied function.

After executing a returned call, send a new completion request with the original messages, the assistant's calls, and a tool result for every call. Use the actual call IDs and arguments returned by the model. For example:

json
{
  "model": "aisha-comet",
  "max_tokens": 256,
  "messages": [
    {"role": "user", "content": "What is the weather in Tashkent?"},
    {
      "role": "assistant",
      "content": "",
      "tool_calls": [
        {
          "id": "call_example",
          "type": "function",
          "function": {"name": "get_weather", "arguments": "{\"city\":\"Tashkent\"}"}
        }
      ]
    },
    {"role": "tool", "tool_call_id": "call_example", "content": "{\"temperature_c\":28}"}
  ]
}

Function arguments are a JSON-encoded string. Complete all pending tool results before adding another conversation message. The follow-up is a new paid generation and needs a new idempotency key. Resending history and tool results adds input tokens to that request.

Get request status ​

GEThttps://api.voicelab.uz/v1/llm/requests/{id}

Read account-scoped LLM status, token usage, and credit details. Requires llm:read.

Use the X-LLM-Request-ID captured from a completion response:

bash
curl -fS "https://api.voicelab.uz/v1/llm/requests/$LLM_REQUEST_ID" \
  -H "Authorization: Bearer $VOICELAB_API_KEY"

The response is a plain object with these fields:

FieldMeaning
id, modelLLM request ID and public model ID
statusrunning, completed, failed, usage_pending, or waived
reserved_creditsWhole credits reserved before generation
charged_creditsWhole credits debited; nullable until settlement
credit_unitsExact request charge in fractional credit units; nullable
credit_unit_scale100000000 units per credit
prompt_tokens, completion_tokensReported input/output counts; nullable
cached_tokens, reasoning_tokensOptional token details represented as nullable counts
elapsed_ms, ttft_msElapsed time and time to first text token in milliseconds; nullable
price_versionPrice version retained for this request
created_atRFC3339 timestamp
failure_codeOptional sanitized diagnostic code, when known
resolution_requiredtrue when usage requires reconciliation

Nullable values mean the corresponding result is not available. A failed request can still have a charge when usage is known. usage_pending means usage needs reconciliation; contact support with the LLM request ID. waived means support has resolved the unknown charge by waiving it.

Only requests belonging to the authenticated account are returned. The endpoint does not return completion text, prompts, reasoning, tool contents, or a full price object. Developer conversation content is not retained in chat tables or request logs. Save responses in your application if you need them later.

Idempotency and retries ​

Send Idempotency-Key for every logical completion request. The header is optional, but without it every call starts a new request. Use 1 to 200 printable ASCII characters with no spaces or control characters. Keys are scoped to the account and API key.

RetryResult
Same key and same request body409 request_already_submitted; no second generation or charge
Same key with changed input or options409 idempotency_conflict
New keyA new generation, subject to admission and billing checks

Both 409 responses include the original X-LLM-Request-ID. Read its status; the API does not store or replay the completion. Keep the same body and key for transport retries. Use a new key only for an intentionally new generation.

A broken stream, disconnect, or missing final usage can leave a request in usage_pending. Independent requests can continue while balance, concurrency and unresolved-exposure limits allow. Exposure is capped at eight unsettled requests per account and upstream model (including active requests). At that limit, new calls for the affected model return 503 llm_model_temporarily_unavailable, error.retryable: false, with no Retry-After. This is an accounting hold, not a timed provider cooldown: support must reconcile or explicitly resolve it. Each model has its own limit. Unresolved requests can block multiple models; switching keys does not bypass the hold. Ask and other products are outside this developer-LLM hold.

A request proven not to have been sent upstream is recorded as failed with failure_code: not_dispatched, its reservation released, without consuming an unresolved slot. A disconnect after dispatch is ambiguous: neither zero visible tokens nor an upstream 499 proves zero cost. The API cancels upstream work on client disconnect and does not silently start another generation. Pending usage still requires reconciliation.

Billing ​

Read current rates from GET /v1/models. Before generation, VoiceLab reserves a conservative input budget plus the maximum output budget and checks the API key's credit limit. This can require more balance than the final charge. Actual reported usage settles the charge and releases unused reserved credits. Each request retains its original price version.

Input includes messages, history, function definitions, and tool results. Output includes generated function arguments and reported reasoning tokens. Reasoning tokens count once within completion tokens; cached input uses the normal input rate. Lower visible output does not necessarily mean lower reasoning usage.

Fractions accumulate per account at 1/100,000,000 of a credit. A small request can debit zero whole credits while adding fractional usage. Whole credits debit when the accumulated amount crosses a whole-credit boundary. Unlimited platform plans do not bypass developer LLM billing.

See pricing and LLM analytics for usage tracking.

Limits ​

LimitValue
HTTP request body128 KiB
Serialized messages and tools64 KiB
Messages1 to 101
Function definitionsUp to 32
Output tokens1 to 4096; default 1024
Completion timeout120 seconds
Concurrent generations70 per account across API replicas; 256 total on each API replica
File parts4 per message, 16 across the conversation; see Files

Rate and capacity limits also apply. Respect Retry-After when present.

The concurrency limit is shared across keys and models. Queue excess work client-side and follow rate and capacity limits. 64 KiB means 65,536 bytes of serialized messages/tools, not tokens or characters. The 4,096-token output cap is an API limit, not model context size.

The 120-second deadline includes waiting for upstream response headers; connection establishment has a shorter timeout. Allow additional network time in your client deadline. A shorter deadline cancels the request. Streaming can show text sooner without reducing total generation time.

Errors ​

LLM handler errors use this envelope:

json
{
  "error": {
    "message": "This request was already submitted. Check X-LLM-Request-ID; no new generation was started.",
    "type": "request_error",
    "code": "request_already_submitted"
  },
  "request_id": "req_example"
}

Shared authentication failures can use the standard error envelope with a top-level message. Read error.code for program logic and handle both message locations.

StatusCodeRecovery
400invalid_requestCorrect JSON, fields, limits, content type, or idempotency key
401invalid_api_keyReplace or correct the developer key
403insufficient_scopeGrant the required LLM permission
402insufficient_credits, api_key_credit_limitCheck balance, key limit, and maximum token budget
404not_foundRefresh models or verify the request ID and account
409request_already_submitted, idempotency_conflictRead the original request status using X-LLM-Request-ID
422llm_request_unsupportedChange settings unsupported by the selected model
429rate_limitedRespect Retry-After when present
503llm_usage_pendingContact support; do not automatically retry
503llm_model_temporarily_unavailableAccount/model unresolved-usage hold; retryable:false, contact support; waiting does not reset it
503llm_unavailableCheck any admitted request's status before a same-key retry
502llm_upstream_canceledUpstream cancellation is ambiguous; inspect the request's usage state before retrying

After SSE starts, an error can arrive as a data event with error.code: "llm_request_incomplete" while HTTP status remains 200. A stream without [DONE] is incomplete. Retain both the HTTP trace request_id, when available, and X-LLM-Request-ID for support.