Skip to content

SDKs

Official clients for TypeScript and Python. Both have retries with backoff, idempotency keys, streaming, typed errors and webhook verification built in. Source: github.com/tovanoai/sdk.

TypeScriptPython
Installnpm install tovanopip install tovano
RuntimesNode 20+, Bun, Deno, Workers, browsersPython 3.9 to 3.14, sync and async
DependenciesNonehttpx only

The npm and PyPI packages are being published. Until then, install from github.com/tovanoai/sdk.

Both read TOVANO_API_KEY and use https://api.tovano.ai by default. Money is always an integer number of micro-dollars in fields ending in _micro.

TypeScript

npm install tovano
import { Tovano, formatUsd } from "tovano";

const tovano = new Tovano(); // reads TOVANO_API_KEY
// or: new Tovano({ apiKey: "tvn_live_...", baseUrl: "https://api.tovano.ai" })

const task = await tovano.tasks.run({
  model: "image/demo-1",
  input: { prompt: "Product photo of a blue ceramic mug", n: 2 },
  customer: "client-0482",
});

console.log(task.status, task.units, formatUsd(task.charged_micro)); // completed 2 $0.04

Tasks

const task = await tovano.tasks.create({
  model: "video/demo-1",
  input: { prompt: "Waves on a beach", duration: 5 },
  webhook_url: "https://example.com/tovano-webhook",
});

const done = await tovano.tasks.wait(task.id, { timeoutMs: 120_000 });
if (done.status === "failed") console.log(done.error?.message, "(not charged)");

Chat, with streaming

for await (const chunk of tovano.chat.completions.create({
  model: "text/demo-chat-1",
  messages: [{ role: "user", content: "Three captions for a mug ad" }],
  stream: true,
})) {
  process.stdout.write(chunk.choices[0]?.delta.content ?? "");
}

Balance, usage and webhooks

const balance = await tovano.balance(); // available_micro, reserved_micro, spent_this_month_micro
const usage = await tovano.usage({ group_by: "customer", from: "2026-11-01" });

import { webhooks } from "tovano";
const event = await webhooks.constructEvent(rawBody, request.headers.get("tovano-signature"), process.env.TOVANO_WEBHOOK_SECRET!);

Python

pip install tovano
from tovano import Tovano, format_usd

client = Tovano()  # reads TOVANO_API_KEY
# or: Tovano(api_key="tvn_live_...", base_url="https://api.tovano.ai")

task = client.tasks.run(
    model="image/demo-1",
    input={"prompt": "Product photo of a blue ceramic mug", "n": 2},
    customer="client-0482",
)
print(task.status, task.units, format_usd(task.charged_micro))  # completed 2 $0.04

Async

import asyncio
from tovano import AsyncTovano

async def main():
    async with AsyncTovano() as client:
        image, voice = await asyncio.gather(
            client.tasks.run(model="image/demo-1", input={"prompt": "Mug"}),
            client.tasks.run(model="audio/demo-tts-1", input={"text": "Hello"}),
        )

asyncio.run(main())

Chat, with streaming

with client.chat.completions.create(
    model="text/demo-chat-1",
    messages=[{"role": "user", "content": "Three captions for a mug ad"}],
    stream=True,
) as stream:
    for chunk in stream:
        if chunk.choices:
            print(chunk.choices[0].delta.content or "", end="")

Errors

Every failed call raises or throws a typed error with status, code and message, the same as the error format.

ErrorStatusCode
AuthenticationError401invalid_api_key
InsufficientCreditsError402insufficient_credits
SpendLimitReachedError402spend_limit_reached
NotFoundError404not_found
InvalidRequestError422invalid_request
RateLimitError429rate_limited (retried for you)
ProviderError502provider_error (not charged)

Connection errors, timeouts, 408, 429 and 5xx are retried with backoff, with the same Idempotency-Key on every attempt.

OpenAI clients

Any OpenAI client works for text models: set its base URL to https://api.tovano.ai/v1 and use your Tovano key. See Chat.

Questions? or write to support@tovano.ai Machine-readable: openapi.json · llms.txt