Skip to content

LLM API

Generate text and function calls with a server-side developer API key. Choose a model, send your conversation, and receive a JSON completion or an SSE stream. Completions spend VoiceLab credits based on token usage. Model discovery and request-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

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.

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 content
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_effortnonone, minimal, low, medium, high, xhigh, or max, subject to model support

Messages support system, user, assistant, and tool roles. Content must be UTF-8 text; images, audio, attachments, and content-part arrays 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. Some models map lower reasoning settings to a lower effort rather than disabling reasoning. Unknown fields, provider URLs/keys, and multiple choices are rejected.

JSON response

With stream: false, a successful 200 response has this shape. Values are illustrative:

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 on JSON success. 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. A network read may split or combine events, so parse SSE boundaries rather than assuming one read is one event.

The final usage chunk appears only with stream_options.include_usage: true. [DONE] is sent after successful credit settlement. 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.

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

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. Further developer LLM generations for that account are blocked until support resolves the usage. Do not retry under new keys to recover it. Ask and other products remain usable.

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 generations2 per account across API replicas

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

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_unavailableCheck any admitted request's status before a same-key retry

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.