Developers

Chitrio API v1

Turn a topic or a screen recording into a finished video from your own backend: script, voice, AI motion on every scene, captions, an optional vertical Short and a YouTube upload. Start a job, get a signed webhook when it is done (or poll), download the MP4.

Base URL https://chitrio.comJSON over HTTPSAPI-key auth

Quick start

Create a key in the app under Settings, then API keys, keep it in an environment variable on your server, and run the three calls below.

End to end: curl
# 0) Optional: price it and check the balance
curl "https://chitrio.com/api/v1/estimate?durationInMinutes=3&generateShort=true" -H "X-Api-Key: $CHITRIO_KEY"
curl https://chitrio.com/api/v1/balance -H "X-Api-Key: $CHITRIO_KEY"

# 1) Start a video (webhookUrl is optional)
curl -X POST https://chitrio.com/api/v1/videos \
  -H "X-Api-Key: $CHITRIO_KEY" -H "Content-Type: application/json" \
  -d '{"topic":"How the fastest trains work","durationInMinutes":3,"language":"en","generateShort":true,
       "webhookUrl":"https://your-server.com/chitrio/webhook"}'
# -> { "jobId": "JOB_ID", "status": "Processing", "title": "..." }

# 2) Wait for the webhook, or poll every 15 to 30 seconds
curl https://chitrio.com/api/v1/videos/JOB_ID -H "X-Api-Key: $CHITRIO_KEY"

# 3) Download with the same key (-L follows the redirect)
curl -L https://chitrio.com/api/v1/videos/JOB_ID/download -H "X-Api-Key: $CHITRIO_KEY" -o video.mp4
curl -L "https://chitrio.com/api/v1/videos/JOB_ID/download?variant=short" -H "X-Api-Key: $CHITRIO_KEY" -o short.mp4

Authentication

Send the workspace's key with every request:

Header
X-Api-Key: yta_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Tools that can only attach a Bearer token can send Authorization: Bearer yta_live_... instead. A key is shown once when it is created (Chitrio keeps only a hash), and workspace admins can create several and revoke any of them at any time.

A key acts on one workspace: its plan, credit balance, voice, brand settings and connected YouTube channel apply to every call made with it.

Endpoints

POST/api/v1/videos

Writes the script (a few seconds), queues the render and answers 202 with a job id.

topicstringrequired

The subject, or a full brief. A length inside the brief ("a 60-second explainer on...") is honoured.

durationInMinutesint

Target length, 1 to 20. Default 5.

languagestring

A render language code from the list below (default "en"). English names like "Hindi" work too; unknown values fall back to English.

generateShortbool

Also cut a vertical 9:16 Short (up to 60 seconds) from the video. Default false.

tonestring

cinematic, educational, conversational, energetic, documentary, storytelling or inspirational. Default: a balanced voice.

visualStylestring

realistic (default), pixar3d, anime, storybook, comic or pixelart.

autoUploadbool

Upload to the workspace's connected YouTube channel when the render finishes. Default false.

autoUploadPrivacystring

private, unlisted or public (default public). Only used with autoUpload. Send private while integrating.

webhookUrlstring

An https URL that receives this job's signed video.completed or video.failed event. Overrides the workspace default for this job. An invalid URL is refused with 400 before anything is spent.

motionMode and motionCoverage are deprecated: accepted and ignored.

202 Accepted
{ "jobId": "3f1c9a0e-...", "status": "Processing", "title": "How the Fastest Trains Work" }
GET/api/v1/videos/{jobId}

Status, progress and the result links.

statusstring

Processing, then Completed, Failed or Cancelled. Only Processing is non-terminal.

progressint

0 to 100.

stagestring

A human-readable label for logs and UIs. Do not parse it.

videoUrlstring | null

Set when Completed. Points at the download endpoint and needs the same API key; it is not a public link.

shortUrlstring | null

Set when Completed and a Short was made. Same rules as videoUrl.

errorstring | null

A readable reason, set only when Failed.

createdAtUtcstring

When the job was created. ISO-8601 in UTC, ending in Z.

updatedAtUtcstring

The last time the job reported progress. ISO-8601 in UTC, ending in Z.

webhookbool

Whether this job sends a webhook event when it ends.

200 OK
{
  "jobId": "3f1c9a0e-...",
  "topic": "How the fastest trains work",
  "status": "Completed",
  "progress": 100,
  "stage": "Done",
  "videoUrl": "https://chitrio.com/api/v1/videos/3f1c9a0e-.../download",
  "shortUrl": "https://chitrio.com/api/v1/videos/3f1c9a0e-.../download?variant=short",
  "error": null,
  "createdAtUtc": "2026-09-30T10:12:44Z",
  "updatedAtUtc": "2026-09-30T10:19:02Z",
  "webhook": true
}
POST/api/v1/videos/{jobId}/cancel

Cancels a job that is still Processing (queued or rendering). Credits already taken for it are returned, exactly as when a render is cancelled in the app. No body. Answers 404 for an unknown job and 409 (with the final status) when it has already finished. A cancelled job sends no webhook.

200 OK
{ "jobId": "3f1c9a0e-...", "status": "Cancelled" }
GET/api/v1/videos/{jobId}/download

The MP4, as a 302 redirect to storage or the file itself (range requests supported). Use a client that follows redirects. Add ?variant=short for the vertical Short. Answers 409 before the job completes, 404 when there is no Short and 410 when the video was deleted.

GET/api/v1/videos?limit=20

Recent jobs of the workspace, newest first. limit 1 to 100. Use createdAtUtc; createdAt is kept for existing integrations and is server time without an offset.

200 OK
[ { "jobId": "3f1c9a0e-...", "topic": "...", "status": "Completed", "progress": 100,
    "createdAt": "2026-09-30T10:12:44", "createdAtUtc": "2026-09-30T10:12:44Z", "updatedAtUtc": "2026-09-30T10:19:02Z" } ]
GET/api/v1/estimate

What a video would cost before you start it, priced the same way the app prices a render. Free: nothing is spent and no script is written. It is an estimate: the exact charge is fixed once the script exists, and anything that does not run is refunded. Query parameters:

topicstring

Optional. A format asked for in the brief (for example a vertical Short) is taken into account.

durationInMinutesint

1 to 20. Default 5.

languagestring

As for POST /api/v1/videos.

generateShortbool

Include the Short. Default false.

visualStylestring

As for POST /api/v1/videos.

Response fields:

totalCreditsint

The estimated charge.

linesarray

The breakdown: [{ label, credits, detail }].

notestring

A short explanation of how the charge is taken.

balanceint

The current balance.

sufficientbool

Whether the balance covers the estimate.

motionFittedbool

True when the balance does not cover AI motion on every scene, so the video would render with AI clips on the key scenes.

isEstimate, basis

Always true; basis is { minutes, scenes } the estimate was priced on.

GET/api/v1/balance

The workspace's credit balance, plan and subscription status (Trialing, Active, PastDue or Canceled).

200 OK
{ "credits": 0, "plan": "Pro", "status": "Active", "currentPeriodEndUtc": "2026-10-30T00:00:00Z" }

Video from a screen recording

Upload a product walkthrough or tutorial recording. Chitrio splits it into scene-length segments, transcribes each one, and then builds a narrated, captioned video on top of your footage, in order.

POST/api/v1/recordings

multipart/form-data with file (.mp4, .mov, .webm or .m4v, up to 100 MB) and optional sceneSeconds (12 to 60, default 25). Answers 201; transcript is null for a silent segment.

201 Created
{
  "recordingId": "b7d0e6c2a4f94d3b9a1e0c5d7f2a8b61",
  "expiresAtUtc": "2026-10-03T10:12:44Z",
  "segments": [
    { "index": 0, "start": 0, "duration": 25, "transcript": "So first we open the products page" },
    { "index": 1, "start": 25, "duration": 21.4, "transcript": null }
  ]
}

Retention: an uploaded recording is kept for 72 hours (expiresAtUtc). After that, creating a video from it answers 410 and it has to be uploaded again. Videos already made from it are not affected.

POST/api/v1/videos/from-recording
recordingIdstringrequired

From POST /api/v1/recordings.

topicstringrequired

The brief for the whole video.

notesarray

Timed notes: [{ "t": 2, "text": "Open the Products page" }], t in seconds from the start.

clipDescriptionsstring[]

One description per segment, in order, exactly as many as there are segments. Use this or notes.

durationInMinutesint

Defaults to the recording's own length.

language, tone, visualStylestring

As for POST /api/v1/videos.

generateShort, autoUpload, autoUploadPrivacy, webhookUrl

As for POST /api/v1/videos.

A silent recording needs notes or clipDescriptions, or the narration can only be generic. Whatever was said on screen is added automatically. Answers 202 with a job id and the scene count; poll it like any video.

Recording flow: curl
# 1) Upload the recording (mp4 / mov / webm / m4v, up to 100 MB)
curl -X POST https://chitrio.com/api/v1/recordings \
  -H "X-Api-Key: $CHITRIO_KEY" -F [email protected] -F sceneSeconds=25
# -> { "recordingId": "REC_ID", "segments": [ { "index": 0, "start": 0, "duration": 25, "transcript": "..." } ] }

# 2) Make the video. Notes say what is on screen; a silent recording needs them.
curl -X POST https://chitrio.com/api/v1/videos/from-recording \
  -H "X-Api-Key: $CHITRIO_KEY" -H "Content-Type: application/json" \
  -d '{"recordingId":"REC_ID","topic":"How to add a product",
       "notes":[{"t":2,"text":"Open the Products page"},{"t":30,"text":"Fill in the price and save"}]}'
# -> { "jobId": "JOB_ID", "status": "Processing", "title": "...", "scenes": 3 }  then poll or wait for the webhook

Webhooks

Instead of polling, Chitrio can POST a signed event to your server when a job completes or fails.

  • Workspace default: Settings, then API keys, then Webhooks. Save an https URL and every job started through this API reports to it. Test sends a signed webhook.test event right away, and Recent deliveries shows each event, its HTTP result and attempts.
  • Per job: send webhookUrl with the request. It wins over the workspace default for that job.
  • Signing secret: in the same place (whsec_...; admins can reveal or rotate it). Every delivery is signed with the workspace's secret.
  • URL rules: https only, no credentials in the URL, and the host must resolve to a public internet address. Redirects are not followed.
The request
POST https://your-server.com/chitrio/webhook
Content-Type: application/json
User-Agent: Chitrio-Webhooks/1.0
X-Chitrio-Event: video.completed
X-Chitrio-Delivery: 5b0e1f2a9c7d4e3f8a6b1c2d3e4f5a6b
X-Chitrio-Signature: t=1790791417,v1=ad4f3d52abcd4c98813782823e92e0a5a52f49bdb0c490834e5f86bfe90eccc2

{
  "event": "video.completed",
  "deliveryId": "5b0e1f2a9c7d4e3f8a6b1c2d3e4f5a6b",
  "jobId": "3f1c9a0e-...",
  "status": "Completed",
  "videoUrl": "https://chitrio.com/api/v1/videos/3f1c9a0e-.../download",
  "shortUrl": null,
  "error": null,
  "occurredAtUtc": "2026-09-30T10:19:05Z"
}
video.completed

The job finished. videoUrl (and shortUrl if a Short was made) need your API key, as in GET /videos/{jobId}.

video.failed

The job failed. error has a readable reason; the credits were returned.

webhook.test

You pressed Test in Settings. status is "Test" and jobId is null.

Delivery and retries

  • Answer with any 2xx within 10 seconds, and do the real work after answering.
  • Anything else (a non-2xx, a timeout, a connection error) is retried 3 times: after about 30 seconds, 2 minutes and 10 minutes. After that the delivery is marked failed in the log; the job is unaffected and the status endpoint still has the result.
  • Delivery is at least once, so the same event can arrive twice. Deduplicate on deliveryId.

Verifying the signature

Scheme
X-Chitrio-Signature: t=<unix seconds>,v1=<hex>

v1 = hex( HMAC-SHA256( key     = your whole secret string, including "whsec_",
                       message = "<t>.<raw request body>" ) )
  1. Read the raw body bytes before any JSON parsing; re-serialising changes the bytes and breaks the check.
  2. Recompute v1 and compare in constant time.
  3. Reject a t more than 5 minutes from your clock (replay protection).
Verify a webhook: Node.js
// Node.js + Express
import crypto from "node:crypto";
import express from "express";

function verifyChitrio(rawBody, header, secret, toleranceSeconds = 300) {
  const fields = {};
  for (const part of (header || "").split(",")) {
    const i = part.indexOf("=");
    if (i > 0) fields[part.slice(0, i).trim()] = part.slice(i + 1).trim();
  }
  const t = fields.t;
  if (!t || !/^\d+$/.test(t) || !fields.v1) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSeconds) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(fields.v1);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();
app.post("/chitrio/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verifyChitrio(raw, req.get("X-Chitrio-Signature"), process.env.CHITRIO_WEBHOOK_SECRET)) {
    return res.sendStatus(400);
  }
  const event = JSON.parse(raw);
  // Deduplicate on event.deliveryId, then queue your work for event.jobId.
  res.sendStatus(200);
});
app.listen(3000);

Rotating the secret: rotate in Settings, then update your server promptly. Deliveries are signed with the new secret from the moment you rotate.

Errors

Errors carry a JSON body { "error": "readable message" }. Nothing is charged for a refused request. A rate-limit 429 also carries a Retry-After header and retryAfterSeconds:

429 Too Many Requests
HTTP/1.1 429 Too Many Requests
Retry-After: 412
Content-Type: application/json

{ "error": "Rate limit reached. Retry after 412 seconds.", "retryAfterSeconds": 412 }
400Missing topic, an invalid field or webhookUrl, or a bad upload. Fix the request; do not retry it unchanged.
401Missing, unknown or revoked key, or a suspended workspace.
402The workspace does not have enough credits to start a render. Top up or upgrade, then retry.
404Unknown job or recording, or it belongs to another workspace.
409The same request was sent moments ago and is still running (poll that job instead), a download was asked for before completion, or a cancel for a job that already finished.
410The video was deleted, or the recording expired. Upload it again.
413Recording over 100 MB. Trim or re-encode it.
429Rate limit reached (wait retryAfterSeconds), or the workspace already has the maximum renders running. Back off and retry.
500The job could not be started. Retry once later; contact support if it persists.
503Rendering is briefly paused or the queue is unusually deep. Retry in a few minutes.
507The workspace's storage is full. Delete old projects in the app, or upgrade.

Rate limits and concurrency

  • Starting work (creating a video, uploading a recording, creating from a recording): 20 requests per 10 minutes per API key. Over it: 429 with Retry-After.
  • Concurrent renders: up to 3 videos per workspace render at once. Another start answers 429 until one finishes.
  • Duplicate guard: sending the identical request again within about 90 seconds, while the first is still running, answers 409. A different language, style or length is a new video and is always allowed.
  • Status, list, download, estimate, balance and cancel calls are not rate limited.

These are defaults and can change. Treat any 429 or 503 as "retry later" with exponential backoff, for example 30, 60, then 120 seconds, and honour Retry-After when present.

Credits and billing

Every render uses the workspace's credits, exactly as a render started in the app does. The cost depends on the video (its length, whether a Short is made, and so on). GET /api/v1/estimate returns it before you start, GET /api/v1/balance returns the balance, and the pricing page lists the plans.

Credits are taken when the render starts and returned automatically if it fails or is cancelled. If the balance is short of what full AI motion would cost, the video still renders with motion fitted to the balance instead of failing. A 402 means there is not enough for any render at all.

Languages, voices and formats

Pass one of these codes as language. The script, narration, captions and on-screen text all come out in that language, in its own script.

enEnglish
hiHindi
mrMarathi
paPunjabi
taTamil
teTelugu
bnBengali
esSpanish
frFrench
deGerman
ptPortuguese
idIndonesian
itItalian
plPolish

Voice and branding (narrator voice, caption style, brand colour, video quality) come from the workspace's settings in the app. Set them once; every API render follows them.

Formats: the main video is a 16:9 MP4. generateShort: true adds a 9:16 vertical cut of up to 60 seconds with captions, ready for Shorts, Reels and Status. Every scene is animated with AI motion; there is nothing to configure.

Polling

Webhooks are the better fit for production. If you poll:

  • A short video takes a few minutes; long videos and busy periods take longer.
  • Poll the job every 15 to 30 seconds and stop when status is no longer Processing.
  • Set your own ceiling (for example 60 minutes) and alert on it rather than polling forever.
  • Store the job id. You can come back to it any time, and the list endpoint shows recent jobs.

Security

  • Keys are secrets. Use them on your server only, never in a browser, a mobile app or a public repository.
  • Use one key per integration and environment, so one can be revoked without touching the others.
  • Rotate by creating a new key, deploying it, then revoking the old one. Settings shows each key's prefix and when it was last used.
  • The video links need the key. To give files to your own users, download them and serve them from your storage.
  • A key only ever sees its own workspace; another workspace's job id answers 404.
  • Verify every webhook's signature and timestamp before trusting it, and keep the signing secret on your server.
  • autoUpload publishes to a real YouTube channel. Use autoUploadPrivacy: "private" while integrating.
  • Report a vulnerability to [email protected] with "SECURITY" in the subject.

Roadmap

Available on request. Tell us which one you need first.

SSO and embed

Your users open Chitrio's editor inside your product, signed in through your identity provider.

Brand kit per request

Voice, logo, colours and caption style chosen per call instead of per workspace.

Ready to integrate?

Create a workspace, add a key under Settings, and send your first topic. Questions from your engineering team go to [email protected].