Docs
BlogHomeStart building

AI (totalum-sdk/ai)


import … from 'totalum-sdk/ai' — or the same namespace on totalum from 'totalum-sdk'. Install, configuration and errors: Totalum SDK.

AI from your server without any provider account: chat completions (streamed or not), structured JSON, a label out of a list, embeddings, image generation and editing, video generation and image description. model is a preset — fast, smart (the default), reasoning, vision, image, video, … each following the newest model of its family — or a pinned model id from models.list(). Every call is billed in Totalum credits and returns them in usage.credits. For a library that wants an OpenAI-compatible client (the Vercel AI SDK, openai), use gatewayConfig().

ts
const { text } = await totalum.ai.chat({ model: 'fast', messages: [{ role: 'user', content: 'Say hi' }] });
Method or typeWhat it does
totalumAiCreates the ai namespace (the same object as totalum.ai, scope ai): a model is a preset (fast, smart, reasoning, vision, image, image-edit, video, tts, stt, embed — each follows the newest model of its family, resolved by Totalum) or a pinned model id; every call returns the Totalum credits it was charged (usage.credits, never tokens).
ai.chatAsks a model and waits for the whole answer: summaries, replies, rewriting, extraction, tool calling.
ai.generateObjectAsks a model for a JSON object that matches your JSON Schema — extract fields from text, classify with details, produce data for your UI.
ai.classifyPicks one label out of your list for an input — route a support ticket, tag a review, detect a language.
ai.embedTurns texts into embedding vectors (lists of numbers whose distance measures meaning).
ai.image.generateGenerates images from a prompt — illustrations, product shots, avatars, hero images — and stores each one as a public project file.
ai.image.editEdits images with a prompt — remove a background, change a colour, put a product in a new scene — and stores the result as a public project file.
ai.video.generateStarts generating a video clip (up to 30 s) from a prompt, optionally from a first/last frame or reference images.
ai.video.getReads a video job: its status and, once completed, the stored clip (video.key, video.url) and its final cost.
ai.video.wait⚠ NODE-ONLY: polls get(jobId) until the job leaves queued/running (default every 5 s, at most 45 min — the longest clip, 30 s at 1080p, renders in about 22 min).
ai.vision.describeDescribes an image in words, or answers a question about it — alt text, moderation, reading a chart, checking a photo.
ai.speech.speakDeprecated: Since 1.0.0: use totalum.speech.synthesize (the same call).
ai.speech.transcribeDeprecated: Since 1.0.0: use totalum.speech.transcribe (the same call).
ai.models.listEach preset's current model and the catalogue of models this project may call (their ids, names, inputs and context sizes) — to let an admin pick a model, or to pin one.
ai.gatewayConfigThe URL and key of Totalum's OpenAI-compatible AI gateway, for any library that takes an OpenAI client: the Vercel AI SDK, openai, LangChain.
AiChatInputA chat request: the model, the conversation and the generation options, in the OpenAI format.
AiEmbedInputThe input of ai.embed().
AiEmbedOutputThe answer of ai.embed().
AiImageEditInputThe input of ai.image.edit().
AiImageInputThe input of ai.image.generate().
AiImageOutputGenerated images, stored as public project files.
AiModelsOutputThe models this project can use, from ai.models.list().
AiPresetNameA model preset: a name that always points to Totalum's current choice for a job, so apps get newer models without code changes.
AiTextOutputA text answer, from ai.vision.describe().
AiVideoCreatedA started video job, from ai.video.generate().
AiVideoInputThe input of ai.video.generate().
AiVideoJobA video job, from ai.video.get() and ai.video.wait().
AiVisionDescribeInputThe input of ai.vision.describe().
ChatResultai.chat(): the first choice read out, the raw completion kept.
ChatStreamai.chat.stream(): the OpenAI-format SSE (raw), its text deltas, and the result once it ends.
ChatUsageWhat a call was charged, in Totalum credits — the only usage Totalum shows (no tokens).
TotalumAiOptionsThe options of totalumAi(): the client options plus the AI gateway URL gatewayConfig() returns.

#totalumAi

ts
totalumAi(options?: TotalumAiOptions): TotalumAi

Creates the ai namespace (the same object as totalum.ai, scope ai): a model is a preset (fast, smart, reasoning, vision, image, image-edit, video, tts, stt, embed — each follows the newest model of its family, resolved by Totalum) or a pinned model id; every call returns the Totalum credits it was charged (usage.credits, never tokens). There is no ai.agent(): an agent loop runs on an OpenAI-compatible library (the template's src/agents/ uses the Vercel AI SDK) pointed at gatewayConfig(), which reaches the same models with the same key, billed in credits. Vector search is totalum.vectors.

Parameters

NameTypeRequiredDescription
optionsTotalumAiOptionsnoThe project key, API URL, environment and AI gateway URL; each falls back to its environment variable.
options.aiBaseUrlstringnoThe OpenAI-compatible AI gateway gatewayConfig() returns; default TOTALUM_AI_BASE_URL, else https://ai.totalum.app/api/v1.
options.keystringnoProject key tlm_pk_…; default TOTALUM_PROJECT_KEY. Server-side only.
options.apiUrlstringnoThe Totalum API's URL: default TOTALUM_SDK_API_URL, else https://sdk-api.totalum.app.
options.env'dev' | 'live'noSent as x-totalum-env on every call (the database it targets, and dev vs live usage); default TOTALUM_ENV, else live.

Returns TotalumAi — The ai namespace: the methods below.

Example

ts
import { totalumAi } from 'totalum-sdk/ai';

const ai = totalumAi();
const { text } = await ai.chat({ messages: [{ role: 'user', content: 'Hello' }] });

The namespace's type is exported as TotalumAi (import type { TotalumAi } from 'totalum-sdk/ai').

#ai.chat

ts
ai.chat(req: { model?, messages, tools?, toolChoice?, parallelToolCalls?, responseFormat?, temperature?, maxTokens?, reasoning?, serverTools?, maxToolCalls?, cache?, zdr?, fastMode?, user? }): Promise<ChatResult>

Asks a model and waits for the whole answer: summaries, replies, rewriting, extraction, tool calling. model is a preset (fast, smart — the default — or reasoning) or a pinned model id from models.list(); messages is the conversation in the OpenAI format. Billed in credits by the model's use. To show the answer while it is written, use chat.stream.

Parameters

NameTypeRequiredDescription
req{ model?, messages, tools?, toolChoice?, parallelToolCalls?, responseFormat?, temperature?, maxTokens?, reasoning?, serverTools?, maxToolCalls?, cache?, zdr?, fastMode?, user? }yesThe model, the conversation and the generation options.
req.modelstringnoA preset (fast, smart, reasoning) or a model id from models.list(). Default smart (with automatic fallbacks). Allowed: at least 1 character.
req.messages({ })[]yesThe conversation, oldest first: { role, content } each. role is system, user, assistant, tool or developer; content is a string, or a list of parts ({ type: "text", text }, { type: "image_url", image_url: { url } }) to send images. Allowed: at least 1 item.
req.tools(Record<string, unknown>)[]noFunctions the model may ask you to call, in the OpenAI format: { type: "function", function: { name, description, parameters } }.
req.toolChoiceunknownnoauto, none, required, or { type: "function", function: { name } } to force one tool.
req.parallelToolCallsbooleannoWhether the model may ask for several tool calls at once.
req.responseFormatRecord<string, unknown>noForce JSON: { type: "json_object" } or { type: "json_schema", json_schema: { name, schema, strict } } (or use generateObject).
req.temperaturenumbernoRandomness, 0–2: lower is more predictable.
req.maxTokensnumbernoThe longest answer, in tokens. Default 4096. Allowed: > 0, ≤ 32768.
req.reasoning{ effort }noHow much the model thinks before answering, for models that support it.
req.reasoning.effortstringyeslow, medium or high.
req.serverTools('web_search' | 'image_generation' | 'web_fetch' | 'datetime')[]noAccepted but not used today.
req.maxToolCallsnumbernoAccepted but not used today. Allowed: > 0, ≤ 30.
req.cache{ ttlSec }noAccepted but not used today.
req.cache.ttlSecnumberyesAccepted but not used today. Allowed: ≥ 1, ≤ 86400.
req.zdrbooleannotrue only uses providers that keep no copy of the prompt (zero data retention).
req.fastModebooleannoAccepted but not used today.
req.userstringnoAn id of your end user, passed to the provider for abuse monitoring.

Returns Promise<ChatResult> — The answer's text, any tool calls, why it stopped, and what it cost.

FieldTypeAlways presentDescription
idstringyesThe completion's id.
modelstringyesThe model that answered (a preset resolves to the model it currently points to).
textstringyesThe answer's text (empty when the model only called tools).
toolCalls({ id, type, function })[]yesThe tools the model asks you to call (with tools); empty otherwise.
toolCalls.idstringyesThe call's id: send the tool's answer back as a tool message with this tool_call_id.
toolCalls.typestringyesAlways function.
toolCalls.function{ name, arguments }yesThe function the model wants to call.
toolCalls.function.namestringyesThe tool's name, as you declared it in tools.
toolCalls.function.argumentsstringyesIts arguments as a JSON string: JSON.parse it.
finishReasonstring | nullyesWhy it stopped: stop (finished), length (hit maxTokens), tool_calls, content_filter, or null.
usageChatUsageyesWhat the call cost.
rawunknownyesThe whole OpenAI-format completion, as received.

Errors — besides the errors every call can throw:

CodeHTTPWhen
AI_MODEL_UNKNOWN400No such preset or model id.
AI_MODEL_NOT_ALLOWED403The model is not available on this plan; details says why.
AI_PROVIDER_UNAVAILABLE502The model did not answer; retry, or use another model.

Examples

ts
const reply = await totalum.ai.chat({
  model: 'fast',
  messages: [
    { role: 'system', content: 'Answer in one word.' },
    { role: 'user', content: 'What is the capital of France?' },
  ],
});

Result:

ts
{
  "id": "gen-1791740364-UbQ0IKv75XZajyP5We6y",
  "model": "openai/gpt-6-luna",
  "text": "Paris",
  "toolCalls": [],
  "finishReason": "stop",
  "usage": { "credits": 0.0007, "inputTokens": 0, "outputTokens": 0 },
  "raw": { "id": "gen-1791740364-UbQ0IKv75XZajyP5We6y", "object": "chat.completion", "choices": [ … ], … }
}
ts
// tool calling: the model asks for getWeather, you run it and send the answer back
const first = await totalum.ai.chat({
  messages: [{ role: 'user', content: 'Weather in Madrid?' }],
  tools: [{ type: 'function', function: { name: 'getWeather', parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] } } }],
});

Result:

ts
{ "text": "", "toolCalls": [{ "id": "call_8f2k", "type": "function", "function": { "name": "getWeather", "arguments": "{\"city\":\"Madrid\"}" } }], "finishReason": "tool_calls", … }

#ai.generateObject

ts
ai.generateObject(req: { schema, prompt?, messages?, model?, healing? }): Promise<{ object, usage }>

Asks a model for a JSON object that matches your JSON Schema — extract fields from text, classify with details, produce data for your UI. The output is checked against the schema (and repaired when slightly off). Pass prompt or messages.

Parameters

NameTypeRequiredDescription
req{ schema, prompt?, messages?, model?, healing? }yesThe schema, the prompt (or messages) and the model.
req.schemaRecord<string, unknown>yesThe JSON Schema of the object (at most 32 KB); use additionalProperties: false and required.
req.promptstringnoThe instruction and the input, as one text (or pass messages).
req.messages({ })[]noThe conversation, instead of prompt.
req.modelstringnoA preset or model id. Default smart.
req.healingbooleannofalse turns off the automatic repair of almost-valid JSON. Default true.

Returns Promise<{ object, usage }> — The object and what it cost.

FieldTypeAlways presentDescription
objectunknownyesThe JSON object, matching schema.
usage{ credits }yesWhat the call cost.
usage.creditsnumberyesCredits charged for this call.

Errors — besides the errors every call can throw:

CodeHTTPWhen
AI_INVALID_SCHEMA502The model did not answer JSON for the schema; details.usage says what was charged.
AI_MODEL_UNKNOWN400No such preset or model id.
AI_PROVIDER_UNAVAILABLE502The model did not answer; retry.

Example

ts
const { object } = await totalum.ai.generateObject({
  model: 'fast',
  prompt: 'Ana is 31 and lives in Madrid.',
  schema: {
    type: 'object',
    properties: { name: { type: 'string' }, age: { type: 'number' }, city: { type: 'string' } },
    required: ['name', 'age', 'city'],
    additionalProperties: false,
  },
});

Result:

json
{ "object": { "name": "Ana", "age": 31, "city": "Madrid" }, "usage": { "credits": 0.0014 } }

#ai.classify

ts
ai.classify(req: { input, options, model?, instructions? }): Promise<{ label }>

Picks one label out of your list for an input — route a support ticket, tag a review, detect a language. Sugar over generateObject.

Parameters

NameTypeRequiredDescription
req{ input, options, model?, instructions? }yesThe input, the labels and optional instructions.
req.inputstringyesThe text to classify.
req.optionsstring[]yesThe labels to choose from.
req.modelstringnoA preset or model id. Default smart.
req.instructionsstringnoHow to choose, in words. Default: "Classify the input into exactly one label."

Returns Promise<{ label }> — { label }: one of options (or '' if the model gave none).

FieldTypeAlways presentDescription
labelstringyesThe chosen label: one of options, or '' when the model gave none.

Errors — besides the errors every call can throw:

CodeHTTPWhen
AI_INVALID_SCHEMA502The model did not answer a label.
AI_MODEL_UNKNOWN400No such preset or model id.

Example

ts
const { label } = await totalum.ai.classify({
  input: 'My card was charged twice',
  options: ['billing', 'bug', 'other'],
  model: 'fast',
});

Result:

json
{ "label": "billing" }

#ai.embed

ts
ai.embed(req: AiEmbedInput): Promise<AiEmbedOutput>

Turns texts into embedding vectors (lists of numbers whose distance measures meaning). To store and search them, totalum.vectors does the embedding for you; use this for your own similarity maths.

Parameters

NameTypeRequiredDescription
reqAiEmbedInputyesThe texts and the model.
req.inputstring | string[]yesOne text, or a list of texts. Allowed: at least 1 character, at least 1 item, at most 256 items.
req.modelstringnoA preset or model id. Default embed. Allowed: at least 1 character.

Returns Promise<AiEmbedOutput> — One vector per input, in order, and what it cost.

FieldTypeAlways presentDescription
embeddings(number[])[]yesOne vector (list of numbers) per input text, in order.
usage{ credits }yesWhat the call cost.
usage.creditsnumberyesCredits charged for this call.

Errors — besides the errors every call can throw:

CodeHTTPWhen
AI_MODEL_UNKNOWN400No such preset or model id.
AI_PROVIDER_UNAVAILABLE502The model did not answer; retry.

Example

ts
const { embeddings } = await totalum.ai.embed({ input: ['red shoes', 'blue sandals'] });

Result:

ts
{ "embeddings": [[0.0138, -0.0121, 0.0125, …], [0.0042, -0.0337, 0.0198, …]], "usage": { "credits": 0.0001 } }

#ai.image.generate

ts
ai.image.generate(req: AiImageInput): Promise<AiImageOutput>

Generates images from a prompt — illustrations, product shots, avatars, hero images — and stores each one as a public project file. Generate once and save the key: never generate on every page view.

Parameters

NameTypeRequiredDescription
reqAiImageInputyesThe prompt, the model, the size and how many.
req.promptstringyesWhat to draw, in words: subject, style, light, composition. Allowed: at least 1 character.
req.modelstringnoA preset or model id. Default image. Allowed: at least 1 character.
req.aspectRatiostringnoThe shape: 1:1, 16:9, 9:16, 4:3, 3:4, … (what the model supports).
req.resolution'1K' | '2K' | '4K' | '512'noThe size class: 512, 1K, 2K or 4K (for models that support it).
req.qualitystringnoA quality level the model supports (low, medium, high).
req.outputFormat'png' | 'jpeg' | 'webp'nopng, jpeg or webp.
req.backgroundstringnotransparent for a transparent background (models that support it), or opaque.
req.nnumbernoHow many images. Default 1. Allowed: ≥ 1, ≤ 4.
req.referencesstring[]noReference images for style or content: project file keys, files URLs or https:// URLs. Allowed: at most 14 items.
req.fileNamestringnoThe base name of the stored files.
req.storebooleannoAccepted for compatibility: images are always stored as project files.

Returns Promise<AiImageOutput> — The stored images and what they cost.

FieldTypeAlways presentDescription
images({ key, url, mediaType, width?, height? })[]yesThe stored images.
images.keystringyesThe stored file's key (see totalum.files).
images.urlstringyesThe image's public URL.
images.mediaTypestringyesThe image's MIME type.
images.widthnumbernoWidth in pixels, when known.
images.heightnumbernoHeight in pixels, when known.
usage{ credits }yesWhat the call cost.
usage.creditsnumberyesCredits charged for this call.
fileNamestringnoThe first image's file name (kept for apps written against the first AI API).
imageUrlstringnoThe first image's URL (kept for apps written against the first AI API).

Errors — besides the errors every call can throw:

CodeHTTPWhen
AI_MODEL_UNKNOWN400No such preset or model id.
AI_MODEL_NOT_ALLOWED403The model is not available on this plan.
AI_PROVIDER_UNAVAILABLE502The model returned no image; retry or change the prompt.

Example

ts
const { images } = await totalum.ai.image.generate({
  prompt: 'A watercolor fox reading a book, warm light',
  aspectRatio: '16:9',
});

Result:

json
{
  "images": [
    { "key": "p/acme-crm/01M4P1J2K3L4M5N6P7Q8R9S0TV.png", "url": "https://files.totalum-project.com/p/acme-crm/01M4P1J2K3L4M5N6P7Q8R9S0TV.png", "mediaType": "image/png", "width": 1344, "height": 768 }
  ],
  "usage": { "credits": 0.2741 },
  "fileName": "01M4P1J2K3L4M5N6P7Q8R9S0TV.png",
  "imageUrl": "https://files.totalum-project.com/p/acme-crm/01M4P1J2K3L4M5N6P7Q8R9S0TV.png"
}

#ai.image.edit

ts
ai.image.edit(req: AiImageEditInput): Promise<AiImageOutput>

Edits images with a prompt — remove a background, change a colour, put a product in a new scene — and stores the result as a public project file.

Parameters

NameTypeRequiredDescription
reqAiImageEditInputyesThe instruction and the images to edit.
req.promptstringyesThe edit, in words. Allowed: at least 1 character.
req.imagesstring[]yesThe images to edit (project file keys, files URLs or https:// URLs). Allowed: at least 1 item, at most 16 items.
req.maskstringnoAccepted but not used today.
req.fileNamestringnoThe base name of the stored files.
req.sizestringnoAccepted but not used today.
req.qualitystringnoA quality level the model supports.
req.outputFormat'png' | 'jpeg' | 'webp'nopng, jpeg or webp.
req.backgroundstringnotransparent or opaque, for models that support it.
req.inputFidelitystringnoAccepted but not used today.

Returns Promise<AiImageOutput> — The stored result image(s) and what they cost.

FieldTypeAlways presentDescription
images({ key, url, mediaType, width?, height? })[]yesThe stored images.
images.keystringyesThe stored file's key (see totalum.files).
images.urlstringyesThe image's public URL.
images.mediaTypestringyesThe image's MIME type.
images.widthnumbernoWidth in pixels, when known.
images.heightnumbernoHeight in pixels, when known.
usage{ credits }yesWhat the call cost.
usage.creditsnumberyesCredits charged for this call.
fileNamestringnoThe first image's file name (kept for apps written against the first AI API).
imageUrlstringnoThe first image's URL (kept for apps written against the first AI API).

Errors — besides the errors every call can throw:

CodeHTTPWhen
AI_MODEL_UNKNOWN400No such preset or model id.
FILE_NOT_FOUND404An input image key does not exist.
AI_PROVIDER_UNAVAILABLE502The model returned no image; retry.

Example

ts
const { images } = await totalum.ai.image.edit({
  prompt: 'Put this sneaker on a white marble pedestal, studio light',
  images: [product.photo.url],
});

Result:

json
{
  "images": [{ "key": "p/acme-crm/01M4P1K3L4M5N6P7Q8R9S0T1UW.png", "url": "https://files.totalum-project.com/p/acme-crm/01M4P1K3L4M5N6P7Q8R9S0T1UW.png", "mediaType": "image/png" }],
  "usage": { "credits": 0.3012 },
  "fileName": "01M4P1K3L4M5N6P7Q8R9S0T1UW.png",
  "imageUrl": "https://files.totalum-project.com/p/acme-crm/01M4P1K3L4M5N6P7Q8R9S0T1UW.png"
}

#ai.video.generate

ts
ai.video.generate(req: AiVideoInput): Promise<AiVideoCreated>

Starts generating a video clip (up to 30 s) from a prompt, optionally from a first/last frame or reference images. It takes minutes: store the jobId and read get(jobId) later (a page refresh, a cron job); the clip is stored as a project file when it completes.

Parameters

NameTypeRequiredDescription
reqAiVideoInputyesThe prompt, the duration and the look.
req.promptstringyesThe scene, in words: subject, action, camera, style. Allowed: at least 1 character.
req.durationSecnumberyesThe clip's length in seconds (the video preset renders 2–30 s). Allowed: > 0, ≤ 30.
req.modelstringnoA preset or model id. Default video. Allowed: at least 1 character.
req.resolutionstringnoThe size the model supports (720p, 1080p).
req.aspectRatiostringno16:9, 9:16, 1:1, … (what the model supports).
req.firstFramestringnoAn image the clip starts from (a project file key or a URL).
req.lastFramestringnoAn image the clip ends on.
req.referencesstring[]noReference images for style or subject.
req.generateAudiobooleannotrue adds a soundtrack (models that support it).
req.seednumbernoA number that makes the result reproducible.

Returns Promise<AiVideoCreated> — The job id and the credits reserved for it (settled when it ends).

FieldTypeAlways presentDescription
jobIdstringyesThe job id: store it and read ai.video.get(jobId) later.
status'queued'yesAlways queued.
estimatedCreditsnumberyesThe credits reserved for the job; the real charge is settled when it ends.

Errors — besides the errors every call can throw:

CodeHTTPWhen
AI_MODEL_UNKNOWN400No such preset or model id.
AI_VIDEO_FAILED502The video could not be started.
AI_MEDIA_PAUSED_THIS_HOUR429Image, video and speech AI are paused until the hour ends.

Example

ts
const job = await totalum.ai.video.generate({
  prompt: 'A paper boat sailing down a rainy street, cinematic',
  durationSec: 5,
  aspectRatio: '16:9',
});
// save job.jobId on the row, then read ai.video.get(jobId) later

Result:

json
{ "jobId": "vid_8m2k4n6p8q0r", "status": "queued", "estimatedCredits": 21.5 }

#ai.video.get

ts
ai.video.get(jobId: string): Promise<AiVideoJob>

Reads a video job: its status and, once completed, the stored clip (video.key, video.url) and its final cost. Call it when the user opens the page or from a cron job until the status is no longer queued/running.

Parameters

NameTypeRequiredDescription
jobIdstringyesThe jobId from video.generate().

Returns Promise<AiVideoJob> — The job's status, and the clip when done.

FieldTypeAlways presentDescription
jobIdstringyesThe job id.
status'expired' | 'completed' | 'queued' | 'running' | 'failed' | 'cancelled'yesqueued and running while it renders; then completed, failed, cancelled or expired.
progressnumbernoHow far it is, 0–1, when the model reports it.
video{ key, url }noThe stored clip, once completed.
video.keystringyesThe stored file's key (see totalum.files).
video.urlstringyesThe clip's public URL (MP4).
usage{ credits }noWhat the call cost.
usage.creditsnumberyesCredits charged for this call.
error{ code, message }noWhy it failed, when failed or expired.
error.codestringyesThe error code (AI_VIDEO_FAILED).
error.messagestringyesWhat happened.

Errors — besides the errors every call can throw:

CodeHTTPWhen
AI_VIDEO_JOB_NOT_FOUND404No such job in this project.
AI_PROVIDER_UNAVAILABLE503The video service could not be reached; retry.

Example

ts
const job = await totalum.ai.video.get('vid_8m2k4n6p8q0r');

Result:

json
{
  "jobId": "vid_8m2k4n6p8q0r",
  "status": "completed",
  "video": { "key": "p/acme-crm/01M4P1M5N6P7Q8R9S0T1U2V3WX.mp4", "url": "https://files.totalum-project.com/p/acme-crm/01M4P1M5N6P7Q8R9S0T1U2V3WX.mp4" },
  "usage": { "credits": 19.8 }
}

#ai.video.wait

ts
ai.video.wait(jobId: string, opts?: { intervalMs?, timeoutMs? }): Promise<AiVideoJob>

⚠ NODE-ONLY: polls get(jobId) until the job leaves queued/running (default every 5 s, at most 45 min — the longest clip, 30 s at 1080p, renders in about 22 min). For scripts (npx tsx scripts/…); in a Worker (the preview and the published app) it throws NODE_ONLY — store the jobId and read get(jobId) later instead.

Parameters

NameTypeRequiredDescription
jobIdstringyesThe jobId from video.generate().
opts{ intervalMs?, timeoutMs? }noHow often and how long to poll.
opts.intervalMsnumbernoMilliseconds between polls. Default 5000.
opts.timeoutMsnumbernoStop polling after this long and return the job as it is. Default 2700000 (45 min).

Returns Promise<AiVideoJob> — The job once it finished (or when the timeout passed).

FieldTypeAlways presentDescription
jobIdstringyesThe job id.
status'expired' | 'completed' | 'queued' | 'running' | 'failed' | 'cancelled'yesqueued and running while it renders; then completed, failed, cancelled or expired.
progressnumbernoHow far it is, 0–1, when the model reports it.
video{ key, url }noThe stored clip, once completed.
video.keystringyesThe stored file's key (see totalum.files).
video.urlstringyesThe clip's public URL (MP4).
usage{ credits }noWhat the call cost.
usage.creditsnumberyesCredits charged for this call.
error{ code, message }noWhy it failed, when failed or expired.
error.codestringyesThe error code (AI_VIDEO_FAILED).
error.messagestringyesWhat happened.

Errors — besides the errors every call can throw:

CodeHTTPWhen
NODE_ONLY—Called inside a Worker; details.replacement says what to do instead.
AI_VIDEO_JOB_NOT_FOUND404No such job in this project.

Example

ts
// scripts/make-intro-video.ts, run with: npx tsx scripts/make-intro-video.ts
const { jobId } = await totalum.ai.video.generate({ prompt: 'Logo reveal on black', durationSec: 5 });
const job = await totalum.ai.video.wait(jobId);
console.log(job.status, job.video?.url);

Result:

ts
completed https://files.totalum-project.com/p/acme-crm/01M4P1M5N6P7Q8R9S0T1U2V3WX.mp4

#ai.vision.describe

ts
ai.vision.describe(req: AiVisionDescribeInput): Promise<AiTextOutput>

Describes an image in words, or answers a question about it — alt text, moderation, reading a chart, checking a photo. The image can be a project file key, a files URL, an https:// URL or a data URL.

Parameters

NameTypeRequiredDescription
reqAiVisionDescribeInputyesThe image, the question and the model.
req.imagestringyesThe image: a project file key, a files URL, an https:// URL, a data: URL or bare base64. Allowed: at least 1 character.
req.promptstringnoThe question or instruction. Default: "Describe this image."
req.modelstringnoA preset or model id. Default vision. Allowed: at least 1 character.

Returns Promise<AiTextOutput> — The answer's text and what it cost.

FieldTypeAlways presentDescription
textstringyesThe answer.
usage{ credits }yesWhat the call cost.
usage.creditsnumberyesCredits charged for this call.

Errors — besides the errors every call can throw:

CodeHTTPWhen
FILE_NOT_FOUND404The image key does not exist.
AI_PROVIDER_UNAVAILABLE502The model could not read the image (for example the URL refused the download); retry with a project file.

Example

ts
const { text } = await totalum.ai.vision.describe({
  image: 'p/acme-crm/01M4P1J2K3L4M5N6P7Q8R9S0TV.png',
  prompt: 'Write alt text for this image in one sentence.',
});

Result:

json
{ "text": "A watercolor fox sits under a lamp reading an open book.", "usage": { "credits": 0.0019 } }

#ai.speech.speak

Deprecated. Since 1.0.0: use totalum.speech.synthesize (the same call).

ts
ai.speech.speak(input: { text, voice?, language?, model?, format?, speed?, store }): Promise<StoredMedia>
ai.speech.speak(input: SpeakInput): Promise<StoredMedia | SpeechAudio>

#ai.speech.transcribe

Deprecated. Since 1.0.0: use totalum.speech.transcribe (the same call).

ts
ai.speech.transcribe(input: TranscribeInput): Promise<TranscribeOutput>

#ai.models.list

ts
ai.models.list(): Promise<AiModelsOutput>

Each preset's current model and the catalogue of models this project may call (their ids, names, inputs and context sizes) — to let an admin pick a model, or to pin one. Free.

Returns Promise<AiModelsOutput> — The presets and the model catalogue.

FieldTypeAlways presentDescription
presets{ image?, fast?, reasoning?, video?, smart?, vision?, image-edit?, tts?, stt?, embed?, embed-image? }yesEach preset and the model id it points to today.
presets.imagestringnoThe image preset's model.
presets.faststringnoThe fast preset's model.
presets.reasoningstringnoThe reasoning preset's model.
presets.videostringnoThe video preset's model.
presets.smartstringnoThe smart preset's model.
presets.visionstringnoThe vision preset's model.
presets.image-editstringnoThe image-edit preset's model.
presets.ttsstringnoThe tts preset's model.
presets.sttstringnoThe stt preset's model.
presets.embedstringnoThe embed preset's model.
presets.embed-imagestringnoThe embed-image preset's model.
models({ slug, name, inputModalities, pricing, contextLength })[]yesEvery model the plan may call.
models.slugstringyesThe model id to pass as model.
models.namestringyesIts display name.
models.inputModalitiesstring[]yesWhat it accepts (text, image, file, audio, video).
models.pricingRecord<string, unknown>yesAlways {}: prices are in credits, per call (usage.credits).
models.contextLengthnumberyesThe most tokens it reads at once.
denyliststring[]noModel ids this project may not use.

Errors — besides the errors every call can throw:

CodeHTTPWhen
AI_PROVIDER_UNAVAILABLE503The model catalogue is unavailable; retry.

Example

ts
const { presets, models } = await totalum.ai.models.list();

Result:

ts
{
  "presets": {
    "fast": "openai/gpt-6-luna",
    "smart": "openai/gpt-6.1-sol",
    "reasoning": "anthropic/claude-opus-5.5",
    "vision": "openai/gpt-6-luna",
    "image": "microsoft/mai-image-2.6-flash",
    "image-edit": "microsoft/mai-image-2.6-flash",
    "video": "alibaba/wan-3.0",
    "tts": "microsoft/mai-voice-2.1-flash",
    "stt": "openai/whisper-large-v3-turbo",
    "embed": "openai/text-embedding-3-small",
    "embed-image": "google/gemini-embedding-2"
  },
  "models": [
    { "slug": "anthropic/claude-haiku-5.5", "name": "Anthropic: Claude Haiku 5.5", "inputModalities": ["text", "image", "file"], "pricing": {}, "contextLength": 1000000 },
    …
  ]
}

#ai.gatewayConfig

ts
ai.gatewayConfig(): { baseURL, apiKey }

The URL and key of Totalum's OpenAI-compatible AI gateway, for any library that takes an OpenAI client: the Vercel AI SDK, openai, LangChain. Same models, same key, billed in credits. Server-side only: the key is the project key. No request is made.

Returns { baseURL, apiKey } — { baseURL, apiKey } to pass to the client.

FieldTypeAlways presentDescription
baseURLstringyesThe gateway's OpenAI-compatible base URL: TOTALUM_AI_BASE_URL, else https://ai.totalum.app/api/v1.
apiKeystringyesThe project key, used as the API key.

Errors — besides the errors every call can throw:

CodeHTTPWhen
SDK_NOT_CONFIGURED—No project key is set.

Examples

ts
import OpenAI from 'openai';

const openai = new OpenAI(totalum.ai.gatewayConfig());
const r = await openai.chat.completions.create({ model: 'fast', messages: [{ role: 'user', content: 'Hi' }] });
ts
import { createOpenAI } from '@ai-sdk/openai';
import { generateText } from 'ai';

const gateway = createOpenAI(totalum.ai.gatewayConfig());
const { text } = await generateText({ model: gateway('smart'), prompt: 'Write a haiku about Mondays' });

Result:

json
{ "baseURL": "https://ai.totalum.app/api/v1", "apiKey": "tlm_pk_…" }

#AiChatInput

A chat request: the model, the conversation and the generation options, in the OpenAI format.

NameTypeRequiredDescription
messages({ })[]yesThe conversation, oldest first: { role, content } each. role is system, user, assistant, tool or developer; content is a string, or a list of parts ({ type: "text", text }, { type: "image_url", image_url: { url } }) to send images. Allowed: at least 1 item.
modelstringnoA preset (fast, smart, reasoning) or a model id from models.list(). Default smart (with automatic fallbacks). Allowed: at least 1 character.
tools(Record<string, unknown>)[]noFunctions the model may ask you to call, in the OpenAI format: { type: "function", function: { name, description, parameters } }.
toolChoiceunknownnoauto, none, required, or { type: "function", function: { name } } to force one tool.
parallelToolCallsbooleannoWhether the model may ask for several tool calls at once.
responseFormatRecord<string, unknown>noForce JSON: { type: "json_object" } or { type: "json_schema", json_schema: { name, schema, strict } } (or use generateObject).
temperaturenumbernoRandomness, 0–2: lower is more predictable.
maxTokensnumbernoThe longest answer, in tokens. Default 4096. Allowed: > 0, ≤ 32768.
reasoning{ effort }noHow much the model thinks before answering, for models that support it.
reasoning.effortstringyeslow, medium or high.
serverTools('web_search' | 'image_generation' | 'web_fetch' | 'datetime')[]noAccepted but not used today.
maxToolCallsnumbernoAccepted but not used today. Allowed: > 0, ≤ 30.
streambooleannoSet by the SDK (chat vs chat.stream); do not pass it.
cache{ ttlSec }noAccepted but not used today.
cache.ttlSecnumberyesAccepted but not used today. Allowed: ≥ 1, ≤ 86400.
zdrbooleannotrue only uses providers that keep no copy of the prompt (zero data retention).
fastModebooleannoAccepted but not used today.
userstringnoAn id of your end user, passed to the provider for abuse monitoring.

#AiEmbedInput

The input of ai.embed().

NameTypeRequiredDescription
inputstring | string[]yesOne text, or a list of texts. Allowed: at least 1 character, at least 1 item, at most 256 items.
modelstringnoA preset or model id. Default embed. Allowed: at least 1 character.

#AiEmbedOutput

The answer of ai.embed().

FieldTypeAlways presentDescription
embeddings(number[])[]yesOne vector (list of numbers) per input text, in order.
usage{ credits }yesWhat the call cost.
usage.creditsnumberyesCredits charged for this call.

#AiImageEditInput

The input of ai.image.edit().

NameTypeRequiredDescription
promptstringyesThe edit, in words. Allowed: at least 1 character.
imagesstring[]yesThe images to edit (project file keys, files URLs or https:// URLs). Allowed: at least 1 item, at most 16 items.
maskstringnoAccepted but not used today.
fileNamestringnoThe base name of the stored files.
sizestringnoAccepted but not used today.
qualitystringnoA quality level the model supports.
outputFormat'png' | 'jpeg' | 'webp'nopng, jpeg or webp.
backgroundstringnotransparent or opaque, for models that support it.
inputFidelitystringnoAccepted but not used today.

#AiImageInput

The input of ai.image.generate().

NameTypeRequiredDescription
promptstringyesWhat to draw, in words: subject, style, light, composition. Allowed: at least 1 character.
modelstringnoA preset or model id. Default image. Allowed: at least 1 character.
aspectRatiostringnoThe shape: 1:1, 16:9, 9:16, 4:3, 3:4, … (what the model supports).
resolution'1K' | '2K' | '4K' | '512'noThe size class: 512, 1K, 2K or 4K (for models that support it).
qualitystringnoA quality level the model supports (low, medium, high).
outputFormat'png' | 'jpeg' | 'webp'nopng, jpeg or webp.
backgroundstringnotransparent for a transparent background (models that support it), or opaque.
nnumbernoHow many images. Default 1. Allowed: ≥ 1, ≤ 4.
referencesstring[]noReference images for style or content: project file keys, files URLs or https:// URLs. Allowed: at most 14 items.
fileNamestringnoThe base name of the stored files.
storebooleannoAccepted for compatibility: images are always stored as project files.

#AiImageOutput

Generated images, stored as public project files.

FieldTypeAlways presentDescription
images({ key, url, mediaType, width?, height? })[]yesThe stored images.
images.keystringyesThe stored file's key (see totalum.files).
images.urlstringyesThe image's public URL.
images.mediaTypestringyesThe image's MIME type.
images.widthnumbernoWidth in pixels, when known.
images.heightnumbernoHeight in pixels, when known.
usage{ credits }yesWhat the call cost.
usage.creditsnumberyesCredits charged for this call.
fileNamestringnoThe first image's file name (kept for apps written against the first AI API).
imageUrlstringnoThe first image's URL (kept for apps written against the first AI API).

#AiModelsOutput

The models this project can use, from ai.models.list().

FieldTypeAlways presentDescription
presets{ image?, fast?, reasoning?, video?, smart?, vision?, image-edit?, tts?, stt?, embed?, embed-image? }yesEach preset and the model id it points to today.
presets.imagestringnoThe image preset's model.
presets.faststringnoThe fast preset's model.
presets.reasoningstringnoThe reasoning preset's model.
presets.videostringnoThe video preset's model.
presets.smartstringnoThe smart preset's model.
presets.visionstringnoThe vision preset's model.
presets.image-editstringnoThe image-edit preset's model.
presets.ttsstringnoThe tts preset's model.
presets.sttstringnoThe stt preset's model.
presets.embedstringnoThe embed preset's model.
presets.embed-imagestringnoThe embed-image preset's model.
models({ slug, name, inputModalities, pricing, contextLength })[]yesEvery model the plan may call.
models.slugstringyesThe model id to pass as model.
models.namestringyesIts display name.
models.inputModalitiesstring[]yesWhat it accepts (text, image, file, audio, video).
models.pricingRecord<string, unknown>yesAlways {}: prices are in credits, per call (usage.credits).
models.contextLengthnumberyesThe most tokens it reads at once.
denyliststring[]noModel ids this project may not use.

#AiPresetName

A model preset: a name that always points to Totalum's current choice for a job, so apps get newer models without code changes. models.list() shows where each points today.

ValueMeaning
'image'Image generation.
'fast'Quick, low-cost text model: classification, short answers, extraction.
'reasoning'The strongest text model, for hard problems (slower, costs more).
'video'Video generation.
'smart'The default text model: good quality for most tasks.
'vision'A model that reads images (used by vision.describe and scan).
'image-edit'Image editing.
'tts'Text to speech.
'stt'Speech to text.
'embed'Text embeddings (used by embed and vectors for text).
'embed-image'Text and image embeddings in one space (used by vectors for images).

#AiTextOutput

A text answer, from ai.vision.describe().

FieldTypeAlways presentDescription
textstringyesThe answer.
usage{ credits }yesWhat the call cost.
usage.creditsnumberyesCredits charged for this call.

#AiVideoCreated

A started video job, from ai.video.generate().

FieldTypeAlways presentDescription
jobIdstringyesThe job id: store it and read ai.video.get(jobId) later.
status'queued'yesAlways queued.
estimatedCreditsnumberyesThe credits reserved for the job; the real charge is settled when it ends.

#AiVideoInput

The input of ai.video.generate().

NameTypeRequiredDescription
promptstringyesThe scene, in words: subject, action, camera, style. Allowed: at least 1 character.
durationSecnumberyesThe clip's length in seconds (the video preset renders 2–30 s). Allowed: > 0, ≤ 30.
modelstringnoA preset or model id. Default video. Allowed: at least 1 character.
resolutionstringnoThe size the model supports (720p, 1080p).
aspectRatiostringno16:9, 9:16, 1:1, … (what the model supports).
firstFramestringnoAn image the clip starts from (a project file key or a URL).
lastFramestringnoAn image the clip ends on.
referencesstring[]noReference images for style or subject.
generateAudiobooleannotrue adds a soundtrack (models that support it).
seednumbernoA number that makes the result reproducible.

#AiVideoJob

A video job, from ai.video.get() and ai.video.wait().

FieldTypeAlways presentDescription
jobIdstringyesThe job id.
status'expired' | 'completed' | 'queued' | 'running' | 'failed' | 'cancelled'yesqueued and running while it renders; then completed, failed, cancelled or expired.
progressnumbernoHow far it is, 0–1, when the model reports it.
video{ key, url }noThe stored clip, once completed.
video.keystringyesThe stored file's key (see totalum.files).
video.urlstringyesThe clip's public URL (MP4).
usage{ credits }noWhat the call cost.
usage.creditsnumberyesCredits charged for this call.
error{ code, message }noWhy it failed, when failed or expired.
error.codestringyesThe error code (AI_VIDEO_FAILED).
error.messagestringyesWhat happened.

#AiVisionDescribeInput

The input of ai.vision.describe().

NameTypeRequiredDescription
imagestringyesThe image: a project file key, a files URL, an https:// URL, a data: URL or bare base64. Allowed: at least 1 character.
promptstringnoThe question or instruction. Default: "Describe this image."
modelstringnoA preset or model id. Default vision. Allowed: at least 1 character.

#ChatResult

ai.chat(): the first choice read out, the raw completion kept.

FieldTypeAlways presentDescription
idstringyesThe completion's id.
modelstringyesThe model that answered (a preset resolves to the model it currently points to).
textstringyesThe answer's text (empty when the model only called tools).
toolCalls({ id, type, function })[]yesThe tools the model asks you to call (with tools); empty otherwise.
toolCalls.idstringyesThe call's id: send the tool's answer back as a tool message with this tool_call_id.
toolCalls.typestringyesAlways function.
toolCalls.function{ name, arguments }yesThe function the model wants to call.
toolCalls.function.namestringyesThe tool's name, as you declared it in tools.
toolCalls.function.argumentsstringyesIts arguments as a JSON string: JSON.parse it.
finishReasonstring | nullyesWhy it stopped: stop (finished), length (hit maxTokens), tool_calls, content_filter, or null.
usageChatUsageyesWhat the call cost.
rawunknownyesThe whole OpenAI-format completion, as received.

#ChatStream

ai.chat.stream(): the OpenAI-format SSE (raw), its text deltas, and the result once it ends.

FieldTypeAlways presentDescription
rawReadableStream<Uint8Array>yesThe OpenAI-format server-sent events, as received: return them from a route (new Response(stream.raw)) to stream to the browser. Read either raw or text(), not both.

#stream.text

ts
stream.text(): AsyncIterable<string>

The answer's text pieces as they arrive (one pass): for await (const piece of stream.text()).

Returns AsyncIterable<string> — An async iterable of text pieces.

Example

ts
const stream = await totalum.ai.chat.stream({ model: 'fast', messages: [{ role: 'user', content: 'Count 1 to 3.' }] });
for await (const piece of stream.text()) process.stdout.write(piece);

Result:

ts
"1" ", " "2" ", " "3" "."

#stream.result

ts
stream.result(): Promise<ChatResult>

The final result once the stream ends, with its credits (reads the rest of the stream if needed).

Returns Promise<ChatResult> — The same result chat() returns; raw holds the accumulated text.

FieldTypeAlways presentDescription
idstringyesThe completion's id.
modelstringyesThe model that answered (a preset resolves to the model it currently points to).
textstringyesThe answer's text (empty when the model only called tools).
toolCalls({ id, type, function })[]yesThe tools the model asks you to call (with tools); empty otherwise.
toolCalls.idstringyesThe call's id: send the tool's answer back as a tool message with this tool_call_id.
toolCalls.typestringyesAlways function.
toolCalls.function{ name, arguments }yesThe function the model wants to call.
toolCalls.function.namestringyesThe tool's name, as you declared it in tools.
toolCalls.function.argumentsstringyesIts arguments as a JSON string: JSON.parse it.
finishReasonstring | nullyesWhy it stopped: stop (finished), length (hit maxTokens), tool_calls, content_filter, or null.
usageChatUsageyesWhat the call cost.
rawunknownyesThe whole OpenAI-format completion, as received.

Errors — besides the errors every call can throw:

CodeHTTPWhen
AI_STREAM_ERROR502The stream broke before the end.

Example

ts
const stream = await totalum.ai.chat.stream({ model: 'fast', messages: [{ role: 'user', content: 'Count 1 to 3.' }] });
const result = await stream.result();

Result:

json
{
  "id": "gen-1791740371-wfMT8WhpKYrAEa1s9XKT",
  "model": "openai/gpt-6-luna",
  "text": "1, 2, 3.",
  "toolCalls": [],
  "finishReason": "stop",
  "usage": { "credits": 0.0005, "inputTokens": 0, "outputTokens": 0 },
  "raw": { "id": "gen-1791740371-wfMT8WhpKYrAEa1s9XKT", "model": "openai/gpt-6-luna", "text": "1, 2, 3.", "choices": [{ "message": { "content": "1, 2, 3." }, "finish_reason": "stop" }] }
}

#ChatUsage

What a call was charged, in Totalum credits — the only usage Totalum shows (no tokens).

FieldTypeAlways presentDescription
creditsnumberyesCredits charged for this call.
inputTokensnumberyesDeprecated. Since 1.1.0: always 0 — Totalum shows only credits.
outputTokensnumberyesDeprecated. Since 1.1.0: always 0 — Totalum shows only credits.

#TotalumAiOptions

The options of totalumAi(): the client options plus the AI gateway URL gatewayConfig() returns.

NameTypeRequiredDescription
aiBaseUrlstringnoThe OpenAI-compatible AI gateway gatewayConfig() returns; default TOTALUM_AI_BASE_URL, else https://ai.totalum.app/api/v1.
keystringnoProject key tlm_pk_…; default TOTALUM_PROJECT_KEY. Server-side only.
apiUrlstringnoThe Totalum API's URL: default TOTALUM_SDK_API_URL, else https://sdk-api.totalum.app.
env'dev' | 'live'noSent as x-totalum-env on every call (the database it targets, and dev vs live usage); default TOTALUM_ENV, else live.