KlemetKlemet home page
VideoImageSpeechStudioNewPricingMCPDocs
Sign inSign up
OverviewQuickstartAuthenticationClaude and ChatGPT
API referenceList modelsCreate a renderRead a renderList rendersWebhooksIdempotency
ReferenceFamiliesErrorsRate limitsPricesVersioning
KlemetKlemet home page

Video, images and voice from open weight models, priced per model.

© 2026 Klemet

Product

  • Pricing
  • Video
  • Image
  • Speech
  • Studio

Account

  • Overview
  • Keys
  • Credits

Developer

  • Documentation
  • API reference

Legal

  • Terms of Service
  • Privacy Policy
  • Refund Policy
  • Acceptable Use Policy
  • Cookie Policy
  • Legal Notice

Klemet

  • About
  • Sign in
  • Create an account
Contact us
  • Home
  • Pricing
  • Create
  • Studio
  • Sign in

Version 2026-09-12

Klemet API

Base URL https://klemet.app/api/v1. All requests and responses are JSON.

Overview

A render takes minutes, and no HTTP request should stay open that long, so the API is asynchronous:

  1. POST /renders accepts your prompt and answers 202 at once with an id.
  2. GET /renders/{id} tells you where it is; when status is succeeded, output.url downloads the file.
  3. Or give a webhook_url and we call you when it is done.

Every render is an object with a state: queued → running → succeeded or failed. A failed render is refunded.

Quickstart

Submit a four-second render, then poll until it is done.

curl
shell
export KLEMET_KEY=klm_live_… # 1. Submitcurl -s -X POST https://klemet.app/api/v1/renders \  -H "Authorization: Bearer $KLEMET_KEY" \  -H "content-type: application/json" \  -d '{"prompt":"A clay amphora on packed earth, warm afternoon light, slow push in.","seconds":4}'# → 202 {"id":"c2737017ed54","status":"queued",...} # 2. Read (repeat every few seconds until status is succeeded or failed)curl -s https://klemet.app/api/v1/renders/c2737017ed54 \  -H "Authorization: Bearer $KLEMET_KEY"# → {"status":"succeeded","output":{"url":"https://…"},"usd":0.014}
Node
http
const KEY = process.env.KLEMET_KEY;const base = "https://klemet.app/api/v1";const headers = { authorization: `Bearer ${KEY}`, "content-type": "application/json" }; const submitted = await fetch(`${base}/renders`, {  method: "POST",  headers,  body: JSON.stringify({ prompt: "A clay amphora on packed earth, warm light.", seconds: 4 }),}).then((r) => r.json()); let render = submitted;while (render.status === "queued" || render.status === "running") {  await new Promise((r) => setTimeout(r, 5000));  render = await fetch(`${base}/renders/${submitted.id}`, { headers }).then((r) => r.json());}console.log(render.status, render.output?.url, render.usd);
Python
python
import os, time, requests KEY = os.environ["KLEMET_KEY"]BASE = "https://klemet.app/api/v1"H = {"Authorization": f"Bearer {KEY}"} render = requests.post(f"{BASE}/renders", headers=H, json={    "prompt": "A clay amphora on packed earth, warm light.",    "seconds": 4,}).json() while render["status"] in ("queued", "running"):    time.sleep(5)    render = requests.get(f"{BASE}/renders/{render['id']}", headers=H).json() print(render["status"], render.get("output", {}).get("url"), render.get("usd"))

Submitting answers 202 in under a second. A short render reaches succeeded in about thirty seconds, and up to two minutes when the model has to load first.

Authentication

Create a key at klemet.app/dashboard/keys. It starts with klm_live_ and is shown once. Send it as a Bearer token on every request:

shell
Authorization: Bearer klm_live_…

Never put the key in a URL and never ship it to a browser: it spends your balance. Lost it? Revoke it and create another. We only store a hash, so nobody can read it back, not even us.

Claude and ChatGPT

Klemet is an MCP server, so you can add it to Claude or ChatGPT and ask for renders in plain language. There is no key to copy: you sign in to Klemet once, in your browser, and the assistant is connected to your account.

http
https://klemet.app/mcp

In Claude, open Settings, then Connectors, Add custom connector, and paste that address. In ChatGPT, add it as a connector the same way. A Klemet page opens, you confirm, and it is done.

The assistant then gets one tool per job: generate_video, generate_image, generate_speech and generate_music to start a render, get_job to follow it, list_models to read the catalogue with its prices, and show_credits to read your balance. Renders are billed to your wallet exactly as they are through the API, so ask for the price before you ask for the render.

List models

GET/models

The catalogue, as the site shows it. Open without a key, so you can read a price before you have an account. Filter with ?modality= (video, image, speech) and ?status= (live or soon).

json
{  "object": "list",  "data": [    {      "id": "lightricks/ltx-2.5",      "object": "model",      "name": "LTX-2.5",      "publisher": "Lightricks",      "modality": "video",      "status": "live",      "licence": "ltx-2.x-community-license-agreement",      "weights_url": "https://huggingface.co/Lightricks/LTX-2.5",      "pricing": { "unit": "/second", "usd": 0.0035 }    }  ],  "has_more": false}

status is live when the model renders today and soon when it is listed but not yet available. A soon model is refused with 400 and carries no price.

Create a render

POST/renders

Request body:

FieldTypeNotes
modelstringA catalogue id, as GET /models returns it. Absent, you get LTX-2.5 in video.
promptstring, requiredWhat the engine reads. Up to 4,000 characters, and 40,000 in speech. English works best.
secondsintegerVideo 4 to 8, default 5. Music 5 to 180, default 30.
width, heightintegerImage only. 256 to 2048 px, rounded to a multiple of 64. Default 1024 each. The price is the one published on the model, per image, whatever the size.
aspectstring16:9, 9:16 or 1:1. Default 16:9.
resolutionstring480p or 720p or 1080p. Default 480p. Video only, and only on the models whose page lists resolution tiers.
seedintegerFix it to reproduce a render. Random when absent.
webhook_urlstringWe POST the finished render there. See Webhooks.

Headers:

HeaderNotes
AuthorizationBearer klm_live_… (required)
Idempotency-KeyOptional. Retry safely. See Idempotency.
X-Klemet-VersionOptional. 2026-09-12 is the only version today.

Response 202 Accepted, with a Location header pointing at the render:

json
{  "id": "c2737017ed54",  "object": "render",  "kind": "video",  "model": "lightricks/ltx-2.5",  "status": "queued",  "created_at": "2026-09-12T15:04:05.000Z",  "finished_at": null,  "input": { "prompt": "…", "seconds": 4, "aspect": "16:9", "seed": 1837201 },  "output": null,  "estimated_usd": 0.014,  "usd": null,  "error": null}

The price is charged to your balance when you submit, and it does not move afterwards. Video is priced per second of output, music per minute, an image per image, and a voice per million characters. Each model publishes its own rate and its own minimum.

Read a render

GET/renders/{id}

Poll every few seconds. When status is succeeded:

json
{  "id": "c2737017ed54",  "object": "render",  "kind": "video",  "model": "lightricks/ltx-2.5",  "status": "succeeded",  "created_at": "2026-09-12T15:04:05.000Z",  "finished_at": "2026-09-12T15:04:41.000Z",  "input": { "prompt": "…", "seconds": 4, "aspect": "16:9", "seed": 1837201 },  "output": {    "url": "https://…r2.cloudflarestorage.com/…",    "expires_at": "2026-09-13T15:04:41.000Z",    "width": 1920, "height": 1088, "seconds": 4  },  "estimated_usd": 0.014,  "usd": 0.014,  "error": null}

output.url is a signed link, valid 24 hours. Download the file and keep it: the link expires, the file does not. When status is failed, error.type says why and you were refunded.

List renders

GET/renders?limit=50&kind={family}&starting_after={id}

Your renders, newest first. 50 per page by default, 100 at most. Filter with kind (video, image, speech).

json
{ "object": "list", "data": [ { "id": "…", "object": "render", … } ], "has_more": false }

To page, pass the last id you received as starting_after.

Webhooks

Give a webhook_url when you submit. When the render ends we POST the same object GET /renders/{id} would return, with two headers:

http
X-Klemet-Event: render.succeeded          (or render.failed)X-Klemet-Signature: t=1789230000,v1=5f1a…   (HMAC-SHA256)

Verify the signature with the webhook signing secret of the key that submitted the render (whsec_…, shown at creation and revealable at klemet.app/dashboard/keys). The signed payload is `${t}.${rawBody}`:

curl
http
# Pseudo-shell: compute HMAC-SHA256 of "<t>.<raw body>" with your whsec_ secret# and compare it, constant-time, with v1. Reject if |now - t| > 300 s.
Node
python
import { createHmac, timingSafeEqual } from "node:crypto"; export function verify(secret, rawBody, header) {  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));  const t = Number(parts.t);  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");  return expected.length === parts.v1.length &&    timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));}
Python
python
import hmac, hashlib, time def verify(secret: str, raw_body: bytes, header: str) -> bool:    parts = dict(p.split("=", 1) for p in header.split(","))    t = int(parts["t"])    if abs(time.time() - t) > 300:        return False    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()    return hmac.compare_digest(expected, parts["v1"])

Answer 2xx quickly and do the work after. Without a 2xx, delivery is retried after 1, 5 and 25 minutes, then stops; GET always works as a fallback.

Idempotency

A connection can drop after we accepted your render but before you read our answer. To retry safely, send an Idempotency-Key header (any unique string up to 255 characters, and a UUID v4 is ideal):

shell
curl -X POST https://klemet.app/api/v1/renders \  -H "Authorization: Bearer $KLEMET_KEY" \  -H "Idempotency-Key: 6f1c1e2a-3b4d-4c5e-9f00-1a2b3c4d5e6f" \  -H "content-type: application/json" \  -d '{"prompt":"…"}'

For 24 hours, the same key with the same body returns the same render, and charges you once. The same key with a different body is refused with 409 idempotency_error.

Families

Pass a model id in model. These are the models the API renders today, and the price each one charges:

FamilyModel idPrice
Videolightricks/ltx-2.5$0.004 /second
Videowan-ai/wan2.2-ti2v-5b$0.049 /second
Imageqwen/qwen-image-2512$0.029 /image
Imageblack-forest-labs/flux.2-klein-4b$0.003 /image
Speechopenbmb/voxcpm2$26.50 /M characters

Leave model out and the render goes to lightricks/ltx-2.5. Any other id is refused with 400, so read GET /models rather than this page: it carries status and the current price for every model.

Errors

Every error is one object. type is for your code, message is for you, param names the field when one is at fault:

json
{  "error": {    "type": "insufficient_funds",    "message": "Your balance will not cover this render. Top up at klemet.app/dashboard/credits.",    "param": null,    "doc_url": "https://docs.klemet.app/#errors"  }}
typeHTTPMeaning
authentication_error401No key, an unknown key, or a revoked key.
invalid_request_error400A missing or malformed field. `param` names it.
insufficient_funds402Your balance cannot cover the estimate. `balance_usd` says what is left.
rate_limit_error429Too many requests. `Retry-After` says when to come back.
not_found404No render with this id on your account.
idempotency_error409This Idempotency-Key was already used with a different body.
engine_error502The engine refused or failed the render. You were not charged.
empty_output200The engine returned an empty clip (status `failed`). You were refunded; submit again.

Rate limits

Per account: 20 requests per minute and 300 per hour. Beyond that, 429 with a Retry-After header in seconds. Ten cards render in parallel; a queue forms above that, which is normal: your renders are not lost, they wait.

Prices

GET /models returns the current price for every model, and each model page carries the same number.

Every render carries its own: estimated_usd when it is accepted, and usd when it lands. Both are what your balance is charged, and neither moves after the fact.

Versioning

This is version 2026-09-12. Send X-Klemet-Version: 2026-09-12 if you want to pin it; every response carries the version it was served with. When something changes in a way that could break you, a new date will be published here and the old one will keep working.