Skip to content

AI Video Generation

Generate short videos from a text prompt. The platform routes the request to one of three providers — Kling OmniVideo, Google Veo, or OpenAI Sora — and supports optional structured controls. When optional parameters are omitted, the selected model infers the best settings. When they are provided, they override the model's inference, subject to each provider's capability limits.

Video generation runs through the same message + streaming pipeline used by all AI capabilities: you send a message with capabilityKey: "ai.video.generate", then subscribe to an SSE stream that publishes progress and the final result.

Endpoints

MethodPathDescriptionAuthRate Limit
POST/ai/sessions/:sessionId/messagesSend a message that triggers a video generation runJWT + Entitlement20/min
GET/ai/sessions/:sessionId/messages/:messageId/streamSSE stream of progress and completion eventsJWT + Entitlement20/min
POST/ai/sessions/:sessionId/messages/:assistantMessageId/stopCancel an in-progress runJWT + Entitlement30/min

Generating a video requires the ai.video.generate entitlement on the active plan.


Provider Selection

Providerprovider valueStrengths
Google VeoveoHigh fidelity, supported aspect ratios 16:9 and 9:16, fixed 8 s at 1080p / 4k
OpenAI SorasoraUp to 20 s, audio supported, image reference supported
Kling OmniVideoklingAspect ratios 16:9, 9:16, 1:1; supports negative prompt, image and video references

If provider is omitted, the platform-configured default provider is used. Explicit but unsupported values return a clear validation error.


Optional Parameters

All optional fields apply to the structured tool input embedded in the message. Send them at the top level of the request body alongside content and capabilityKey, or let the model infer when omitted.

FieldTypeAllowed valuesNotes
providerstringkling | veo | soraDefaults to configured provider
aspectRatiostring16:9, 9:16, 1:1Provider-dependent; 1:1 only on Kling
durationnumbersecondsSora ≤ 20 s; Veo at 1080p/4k = 8 s; Kling ∈
sizestringWIDTHxHEIGHTSora only; takes priority over aspectRatio
resolutionstring480p, 720p, 1080p, 4kVeo and Sora
negativePromptstringfree-formKling only
seednumberintegerWhere supported by the provider
generateAudiobooleanSora and Kling
inputImageUrlstringimage URLReference / starting frame
inputVideoUrlstringvideo URLReference clip (Kling)
referenceImagesstring[]image URLsMultiple references
referenceVideosstring[]video URLsMultiple references (Kling)
styleTokensstring[]free-form hintsLighting, mood, cinematic adjectives
modelstringprovider model keyOverride the default model for the chosen provider
metadataobjectfree-formEchoed back in the completion event for client correlation

Aspect ratios 4:3, 3:4, and 21:9 are accepted by the schema for forward compatibility but are not yet exposed in the standard UI and will be dropped if the chosen provider does not support them.


POST /ai/sessions/:sessionId/messages

Send a user message that triggers a video generation run. The request returns immediately with the assistant message ID and a run ID; subscribe to the SSE stream to receive progress and the final video URL.

Request

Authentication: JWT Bearer token + active AI entitlement (ai.video.generate). Rate limit: 20/min.

Request Body

FieldTypeRequiredDescription
contentstringYesFree-text prompt describing the desired video
capabilityKeystringYes (for deterministic routing)Set to ai.video.generate to skip intent detection
providerstringNoOne of kling, veo, sora
modelstringNoProvider-specific model identifier
aspectRatiostringNoOne of 16:9, 9:16, 1:1, 4:3, 3:4, 21:9 (provider-dependent)
durationnumberNoVideo duration in seconds (1–60). Numeric strings are accepted and coerced (e.g. "12"12).
sizestringNoExact pixel size as WIDTHxHEIGHT (Sora)
resolutionstringNo480p, 720p, 1080p, or 4k
negativePromptstringNoWhat the video should avoid (Kling only); max 4000 chars
seednumberNoRandom seed for reproducibility (integer; numeric strings coerced)
generateAudiobooleanNoGenerate an audio track alongside the video
inputImageUrlstringNohttp(s) URL of a reference / starting image
inputVideoUrlstringNohttp(s) URL of a reference / starting video
referenceImagesstring[]Nohttp(s) URLs of reference images (max 8)
referenceVideosstring[]Nohttp(s) URLs of reference videos (max 4)
styleTokensstring[]NoStylistic hints (max 16 items, ≤ 64 chars each)
metadataobjectNoFree-form metadata echoed in the completion event
effortModestringNobasic, thinking, or pro — routes model selection
idempotencyKeystringNoClient-generated key to dedupe retries

The authenticated request supplies the actor — request bodies never include accountId, chainerId, or workspaceId.

Response

json
{
  "data": {
    "sessionId": "550e8400-e29b-41d4-a716-446655440000",
    "userMessage": {
      "id": "550e8400-e29b-41d4-a716-446655440010",
      "role": "user",
      "content": "A serene sunset over snow-capped mountains, cinematic.",
      "createdAt": "2026-05-22T10:00:00Z",
      "toolCalls": []
    },
    "assistantMessage": {
      "id": "550e8400-e29b-41d4-a716-446655440011",
      "role": "assistant",
      "content": "",
      "createdAt": "2026-05-22T10:00:00Z",
      "runId": "550e8400-e29b-41d4-a716-446655440012",
      "status": "pending",
      "toolCalls": [],
      "modelName": "Veo",
      "providerKey": "google"
    },
    "run": {
      "id": "550e8400-e29b-41d4-a716-446655440012",
      "status": "running"
    },
    "resolvedModel": {
      "modelKey": "veo-2.0-generate-001",
      "displayName": "Google Veo",
      "provider": "google",
      "reason": "explicit",
      "requestedModelKey": null,
      "missingCapabilities": []
    }
  }
}

Error Responses

HTTPMeaning
400Schema validation failed, or an unsupported provider value was sent
401Missing or invalid JWT
402Insufficient credits to reserve the requested duration
400Capability-aware validation error — for example sending bpm with ai.video.generate returns bpm is only valid for ai.audio.generate in details.fields[0].message. See Messages: Capability Parameters.
403Plan does not include ai.video.generate
404sessionId not found
429Rate limit exceeded

Code Examples

bash
curl -X POST "$BASE_URL/ai/sessions/$SESSION_ID/messages" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "A serene sunset over snow-capped mountains, cinematic.",
    "capabilityKey": "ai.video.generate"
  }'
bash
curl -X POST "$BASE_URL/ai/sessions/$SESSION_ID/messages" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hand-drawn animation of a fox chasing leaves through a forest.",
    "capabilityKey": "ai.video.generate",
    "provider": "sora",
    "aspectRatio": "9:16",
    "resolution": "720p",
    "duration": 12,
    "generateAudio": true
  }'
bash
curl -X POST "$BASE_URL/ai/sessions/$SESSION_ID/messages" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Extend this clip with the same style for another 5 seconds.",
    "capabilityKey": "ai.video.generate",
    "provider": "kling",
    "aspectRatio": "16:9",
    "duration": 5,
    "negativePrompt": "low quality, watermark",
    "referenceImages": ["https://cdn.example.com/style-frame.png"]
  }'
javascript
const res = await fetch(
  `${BASE_URL}/ai/sessions/${sessionId}/messages`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      content: "A timelapse of a city skyline at golden hour.",
      capabilityKey: "ai.video.generate",
      provider: "veo",
      aspectRatio: "16:9",
      resolution: "1080p",
      duration: 8,
    }),
  },
);
const { data } = await res.json();
const { assistantMessage, run } = data;
console.log("Run id:", run.id, "stream:", assistantMessage.id);
python
import requests

res = requests.post(
    f"{BASE_URL}/ai/sessions/{session_id}/messages",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
    },
    json={
        "content": "A drone shot of waves crashing on a rocky shore.",
        "capabilityKey": "ai.video.generate",
        "provider": "sora",
        "resolution": "720p",
        "aspectRatio": "16:9",
        "duration": 10,
    },
)
body = res.json()["data"]
assistant_id = body["assistantMessage"]["id"]
run_id = body["run"]["id"]

GET /ai/sessions/:sessionId/messages/:messageId/stream

Open a Server-Sent Events stream for an in-progress assistant message. The stream stays open for the lifetime of the run and closes shortly after the terminal event.

Request

Authentication: JWT Bearer token + active AI entitlement. Rate limit: 20/min.

Response

Event sequence — video generation

text
event: run.started
event: tool.started        toolKey=media.video.generate
event: tool.progress       message="Queued video generation..."
event: tool.progress       message="Generating video..."
event: tool.progress       message="Saving generated video..."
event: tool.completed      toolKey=media.video.generate  data={ videoUrl, ... }
event: message.delta       (assistant prose summarising the result)
event: message.completed
event: run.settlement.completed

The number and timing of tool.progress events depend on the provider — stage labels are stable, percentages are not.

Event payload — tool.completed

json
{
  "eventId": 6,
  "type": "tool.completed",
  "runId": "550e8400-e29b-41d4-a716-446655440012",
  "stepId": "tc_abc123",
  "timestamp": "2026-05-22T10:00:14Z",
  "payload": {
    "toolKey": "media.video.generate",
    "callId": "tc_abc123",
    "executionMs": 12480,
    "success": true,
    "data": {
      "success": true,
      "provider": "veo",
      "model": "veo-2.0-generate-001",
      "videoUrl": "https://media.chainabit.com/ai-generated/...mp4",
      "thumbnailUrl": null,
      "duration": 8,
      "aspectRatio": "16:9",
      "size": null,
      "resolution": "1080p",
      "storageKey": "ai/generated/video/...",
      "externalJobId": "veo-op-1234",
      "status": "completed"
    }
  }
}

When the provider returns status: "pending" (typical for long-running jobs that exceed the polling window), poll the message via GET /ai/sessions/:sessionId/messages/:messageId to retrieve the final URL once available.

Code Examples

javascript
const stream = new EventSource(
  `${BASE_URL}/ai/sessions/${sessionId}/messages/${assistantId}/stream`,
  { withCredentials: true },
);

stream.addEventListener("tool.progress", (event) => {
  const { payload } = JSON.parse(event.data);
  if (payload.activityType === "video_generation") {
    console.log("progress:", payload.message);
  }
});

stream.addEventListener("tool.completed", (event) => {
  const { payload } = JSON.parse(event.data);
  if (payload.toolKey === "media.video.generate") {
    console.log("Video ready:", payload.data.videoUrl);
  }
});

stream.addEventListener("run.settlement.completed", () => stream.close());
python
import sseclient
import requests, json

response = requests.get(
    f"{BASE_URL}/ai/sessions/{session_id}/messages/{assistant_id}/stream",
    headers={"Authorization": f"Bearer {TOKEN}"},
    stream=True,
)
client = sseclient.SSEClient(response)
for event in client.events():
    if event.event == "tool.completed":
        payload = json.loads(event.data)["payload"]
        if payload["toolKey"] == "media.video.generate":
            print("video:", payload["data"]["videoUrl"])
            break

POST /ai/sessions/:sessionId/messages/:assistantMessageId/stop

Cancel an in-progress video generation run. The partial state is finalised with status: "stopped". Reserved credits are released for runs that have not yet completed; runs that have already produced an output are kept and charged normally.

Request

Authentication: JWT Bearer token + active AI entitlement. Rate limit: 30/min.

Response

json
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440011",
    "status": "stopped"
  }
}

Code Example

bash
curl -X POST "$BASE_URL/ai/sessions/$SESSION_ID/messages/$ASSISTANT_ID/stop" \
  -H "Authorization: Bearer $TOKEN"

Behaviour Notes

  • Backward compatibility: Sending { content, capabilityKey: "ai.video.generate" } alone still works. All structured parameters are optional.
  • Conflicts: When both size and aspectRatio are provided to a provider that supports size, size wins; the conflict is recorded in the run's metadata.
  • Clamping: Out-of-range durations and unsupported sizes are clamped to provider-safe values where possible, otherwise rejected with a clear error.
  • Storage: Generated videos are uploaded to the workspace's media bucket and returned as a time-limited URL. To refresh the URL after it expires, fetch the corresponding generation record via the message resource.
  • Aspect ratios beyond 16:9, 9:16, 1:1 are accepted by the schema for forward compatibility but are not yet exposed in default UI surfaces.

  • AI Messages — generic message / streaming pipeline used by all AI capabilities.
  • AI Sessions — create the session that hosts a video run.
  • Generative Media API — alternative, context-aware media pipeline driven by per-source consent.

Built with purpose.