Quick Start
Validate a five-second video, export it, and save the MP4 with cURL, JavaScript, or Python.
Create a five-second video with text and a solid background. The sample has no external media dependencies and exports at 1280 × 720, 30 fps.
You need an Indream API key and enough workspace credits for a cloud export. Choose one example below; each validates the same video, creates an export, waits for completion, and saves hello-indream.mp4.
1. Download the sample
curl --fail --output editor-state.json https://docs.indream.ai/examples/editor-state.jsonThe sample editor state is a complete JSON document. Keep it in the same directory as your script.
2. Validate, export, and save
Replace <YOUR_API_KEY> and run your chosen script on your server.
Use Node.js 18 or later. Install the SDK:
pnpm add @indreamai/client@1.1.0import { readFileSync, writeFileSync } from 'node:fs'
import { IndreamClient } from '@indreamai/client'
const client = new IndreamClient({ apiKey: '<YOUR_API_KEY>' })
const editorState = JSON.parse(readFileSync('editor-state.json', 'utf8'))
const validation = await client.editor.validate(editorState)
if (!validation.valid) {
throw new Error(JSON.stringify(validation.errors))
}
const created = await client.exports.create({
editorState,
stateSchemaVersion: 'v1',
ratio: '16:9',
scale: 1,
fps: 30,
format: 'mp4',
filename: 'hello-indream.mp4',
})
console.log('Export:', created.taskId)
const completed = await client.exports.wait(created.taskId, {
timeoutMs: 600_000,
pollIntervalMs: 2000,
})
if (!completed.outputUrl) throw new Error('The export has no download URL.')
const response = await fetch(completed.outputUrl)
if (!response.ok) throw new Error('The exported file could not be downloaded.')
writeFileSync('hello-indream.mp4', Buffer.from(await response.arrayBuffer()))
console.log('Saved hello-indream.mp4')node quick-start.mjsUse Python 3.10 or later. Install the SDK:
python -m pip install indream-client==1.1.0import json
from pathlib import Path
import httpx
from indream import IndreamClient
editor_state = json.loads(Path("editor-state.json").read_text())
with IndreamClient(api_key="<YOUR_API_KEY>") as client:
validation = client.editor.validate(editor_state)
if not validation.valid:
raise ValueError(validation.errors)
created = client.exports.create({
"editorState": editor_state,
"stateSchemaVersion": "v1",
"ratio": "16:9",
"scale": 1,
"fps": 30,
"format": "mp4",
"filename": "hello-indream.mp4",
})
print("Export:", created.task_id)
completed = client.exports.wait(created.task_id, timeout=600, poll_interval=2)
if not completed.output_url:
raise ValueError("The export has no download URL.")
with httpx.stream("GET", completed.output_url, timeout=60) as response:
response.raise_for_status()
with Path("hello-indream.mp4").open("wb") as output:
for chunk in response.iter_bytes():
output.write(chunk)
print("Saved hello-indream.mp4")python quick_start.pyUse Bash, cURL 7.76 or later, jq, and uuidgen. Save the script as quick-start.sh:
#!/usr/bin/env bash
set -euo pipefail
api_key='<YOUR_API_KEY>'
api_url='https://api.indream.ai'
idempotency_key="$(uuidgen)"
printf '%s\n' "$idempotency_key" > export-idempotency-key.txt
validation="$(curl --fail-with-body --silent --show-error --max-time 60 \
"$api_url/v1/editor/validate" \
-H "Authorization: Bearer $api_key" \
-H 'Content-Type: application/json' \
--data-binary "$(jq -c '{editorState: .}' editor-state.json)")"
if ! jq -e '.data.valid == true' <<< "$validation" > /dev/null; then
jq '.data.errors' <<< "$validation" >&2
exit 1
fi
jq '{editorState: ., stateSchemaVersion: "v1", ratio: "16:9", scale: 1,
fps: 30, format: "mp4", filename: "hello-indream.mp4"}' \
editor-state.json > export-request.json
created="$(curl --fail-with-body --silent --show-error --max-time 60 \
"$api_url/v1/exports" \
-H "Authorization: Bearer $api_key" \
-H "Idempotency-Key: $idempotency_key" \
-H 'Content-Type: application/json' \
--data-binary @export-request.json)"
task_id="$(jq -er '.data.taskId' <<< "$created")"
printf 'Export: %s\n' "$task_id"
deadline=$((SECONDS + 600))
while (( SECONDS < deadline )); do
task="$(curl --fail-with-body --silent --show-error --max-time 60 \
"$api_url/v1/exports/$task_id" \
-H "Authorization: Bearer $api_key")"
status="$(jq -er '.data.status' <<< "$task")"
case "$status" in
COMPLETED)
output_url="$(jq -er '.data.outputUrl | select(type == "string" and length > 0)' <<< "$task")"
curl --fail --silent --show-error --max-time 300 --output hello-indream.mp4 "$output_url"
printf 'Saved hello-indream.mp4\n'
exit 0
;;
FAILED|CANCELED)
jq -r '.data.error // .data.status' <<< "$task" >&2
exit 1
;;
PENDING|PROCESSING|PAUSED) sleep 2 ;;
*) printf 'Unexpected task status: %s\n' "$status" >&2; exit 1 ;;
esac
done
printf 'Stopped waiting. Export %s may still be running.\n' "$task_id" >&2
exit 1bash quick-start.shThe script creates an idempotency key once. If you retry its create request, reuse the value in export-idempotency-key.txt and the unchanged export-request.json. Running the entire script again creates a new export.
Creation returns a task ID before rendering finishes. The examples stop on completion, failure, or cancellation. A ten-minute wait timeout stops local polling; it does not cancel the server task. Download and save files your application needs to retain.
3. Make it yours
Change the sample text, colors, or duration, then validate again. Editor state explains the fields and timing units.
Use Projects to save an editable draft, Assets to upload your own media, or Webhooks to receive export results without keeping a polling process running.
An export consumes workspace credits. If a request fails, follow the action for its error code in Errors and retries. For create retries, keep the same idempotency key and unchanged payload to avoid duplicate tasks.
Last updated on