Developers
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.
https://chitrio.comJSON over HTTPSAPI-key authCreate 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.
# 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.mp4Send the workspace's key with every request:
X-Api-Key: yta_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTools 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.
Writes the script (a few seconds), queues the render and answers 202 with a job id.
topicstringrequiredThe subject, or a full brief. A length inside the brief ("a 60-second explainer on...") is honoured.
durationInMinutesintTarget length, 1 to 20. Default 5.
languagestringA render language code from the list below (default "en"). English names like "Hindi" work too; unknown values fall back to English.
generateShortboolAlso cut a vertical 9:16 Short (up to 60 seconds) from the video. Default false.
tonestringcinematic, educational, conversational, energetic, documentary, storytelling or inspirational. Default: a balanced voice.
visualStylestringrealistic (default), pixar3d, anime, storybook, comic or pixelart.
autoUploadboolUpload to the workspace's connected YouTube channel when the render finishes. Default false.
autoUploadPrivacystringprivate, unlisted or public (default public). Only used with autoUpload. Send private while integrating.
webhookUrlstringAn 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.
{ "jobId": "3f1c9a0e-...", "status": "Processing", "title": "How the Fastest Trains Work" }Status, progress and the result links.
statusstringProcessing, then Completed, Failed or Cancelled. Only Processing is non-terminal.
progressint0 to 100.
stagestringA human-readable label for logs and UIs. Do not parse it.
videoUrlstring | nullSet when Completed. Points at the download endpoint and needs the same API key; it is not a public link.
shortUrlstring | nullSet when Completed and a Short was made. Same rules as videoUrl.
errorstring | nullA readable reason, set only when Failed.
createdAtUtcstringWhen the job was created. ISO-8601 in UTC, ending in Z.
updatedAtUtcstringThe last time the job reported progress. ISO-8601 in UTC, ending in Z.
webhookboolWhether this job sends a webhook event when it ends.
{
"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
}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.
{ "jobId": "3f1c9a0e-...", "status": "Cancelled" }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.
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.
[ { "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" } ]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:
topicstringOptional. A format asked for in the brief (for example a vertical Short) is taken into account.
durationInMinutesint1 to 20. Default 5.
languagestringAs for POST /api/v1/videos.
generateShortboolInclude the Short. Default false.
visualStylestringAs for POST /api/v1/videos.
Response fields:
totalCreditsintThe estimated charge.
linesarrayThe breakdown: [{ label, credits, detail }].
notestringA short explanation of how the charge is taken.
balanceintThe current balance.
sufficientboolWhether the balance covers the estimate.
motionFittedboolTrue when the balance does not cover AI motion on every scene, so the video would render with AI clips on the key scenes.
isEstimate, basisAlways true; basis is { minutes, scenes } the estimate was priced on.
The workspace's credit balance, plan and subscription status (Trialing, Active, PastDue or Canceled).
{ "credits": 0, "plan": "Pro", "status": "Active", "currentPeriodEndUtc": "2026-10-30T00:00:00Z" }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.
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.
{
"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.
recordingIdstringrequiredFrom POST /api/v1/recordings.
topicstringrequiredThe brief for the whole video.
notesarrayTimed 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.
durationInMinutesintDefaults to the recording's own length.
language, tone, visualStylestringAs for POST /api/v1/videos.
generateShort, autoUpload, autoUploadPrivacy, webhookUrlAs 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.
# 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 webhookInstead of polling, Chitrio can POST a signed event to your server when a job completes or fails.
webhook.test event right away, and Recent deliveries shows each event, its HTTP result and attempts.webhookUrl with the request. It wins over the workspace default for that job.whsec_...; admins can reveal or rotate it). Every delivery is signed with the workspace's secret.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.completedThe job finished. videoUrl (and shortUrl if a Short was made) need your API key, as in GET /videos/{jobId}.
video.failedThe job failed. error has a readable reason; the credits were returned.
webhook.testYou pressed Test in Settings. status is "Test" and jobId is null.
deliveryId.X-Chitrio-Signature: t=<unix seconds>,v1=<hex>
v1 = hex( HMAC-SHA256( key = your whole secret string, including "whsec_",
message = "<t>.<raw request body>" ) )t more than 5 minutes from your clock (replay protection).// 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 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:
HTTP/1.1 429 Too Many Requests
Retry-After: 412
Content-Type: application/json
{ "error": "Rate limit reached. Retry after 412 seconds.", "retryAfterSeconds": 412 }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.
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.
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.
enEnglishhiHindimrMarathipaPunjabitaTamilteTelugubnBengaliesSpanishfrFrenchdeGermanptPortugueseidIndonesianitItalianplPolishVoice 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.
Webhooks are the better fit for production. If you poll:
status is no longer Processing.autoUpload publishes to a real YouTube channel. Use autoUploadPrivacy: "private" while integrating.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.
Create a workspace, add a key under Settings, and send your first topic. Questions from your engineering team go to [email protected].