BallEngine

Developer API and AI agent guide

The BallEngine Developer API and official MCP server let you create and render physics simulation MP4 videos from code, rendered in the cloud, using any of the public modes. Choose a duration from 5s to 300s, an aspect ratio (9:16, 16:9, 1:1 and more), music you compose or upload, visual themes, captions and speed.
Aspect ratios
9:16 (Shorts/Reels/TikTok), 16:9 (YouTube), 1:1 (Square), 4:5, and 3:4.
Your own music
Compose to a mood such as dark phonk, synthwave or chill lofi, or use a track from your own library.
MCP server
A Model Context Protocol server for Claude Desktop, Cursor and other AI agents.

Authentication

Create a secret key on your Developer page and send it as a bearer token on every request. Keys are shown once at creation and can be revoked at any time. Keep them server-side: never ship a key inside a browser or mobile app.

Authorization: Bearer be_sk_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Pricing

Rendering is billed with prepaid credits using linear pricing: 1 credit = 30 seconds of 720p 30 fps video, 2 credits = 30 seconds of 1080p 60 fps (at 720p 30 fps: 15s = 0.5 credits, 45s = 1.5 credits, 60s = 2.0 credits, 300s = 10.0 credits; double each at 1080p 60 fps). You can generate any clip length from 5 seconds up to 5 minutes (300 seconds). Clips shorter than 10 seconds are billed as 10 seconds.

Create a video

POST https://ballengine.app/api/v1/videos
Content-Type: application/json
Authorization: Bearer be_sk_...

{
  "duration": 30,
  "quality": "1080p60",
  "aspect_ratio": "9:16",
  "mode": "GHOST_ESCAPE",
  "theme": "cyberpunk_neon",
  "music_mood": "dark phonk",
  "title_text": "Wait for the end!",
  "watermark_text": "@MyChannel",
  "speed": 1.2
}

Parameters

FieldTypeDescription
durationnumberDuration in seconds (5 to 300, default: 30). Linear billing by quality: 1 credit per 30s at 720p30, 2 credits per 30s at 1080p60.
qualitystring"720p30" (720p at 30 fps, 1 credit per 30s, default) or "1080p60" (1080p at 60 fps, 2 credits per 30s).
aspect_ratiostring"9:16", "16:9", "1:1", "4:5" or "3:4". The short edge is 720 at 720p30 and 1080 at 1080p60, so "9:16" is 720x1280 or 1080x1920.
modestring"GHOST_ESCAPE", "SPACE_SPIRAL", "RHYTHMIC", "ESCAPE", "DRAWER", "SPAWNER", "WAVE_GRID", "FLAG_BATTLE", "MARBLE_RUN_3D", or "RANDOM".
themestring"cyberpunk_neon", "dark_minimal", "retro_synthwave", "matrix_code", "gold_luxury", "fire_and_ice", "pastel_dream", "deep_space".
music_moodstringMood to compose in when no track is named (e.g. "dark phonk", "synthwave", "chill lofi", "epic battle").
songstringName of a track in your own library, one you composed in the studio or uploaded yourself.
xmidi_tokenstringLegacy catalogue token, kept so existing projects keep rendering. Not available for new work. Compose a track or upload your own instead.
title_textstringOn-screen header text overlay.
watermark_textstringCustom creator watermark / social handle.
speednumberSimulation speed multiplier (0.5 to 3.0, default: 1.0).
randomize_depthstringRandomization depth: "LIGHT", "MODERATE", "DEEP" (default), "TOTAL", or "CUSTOM".
randomize_groupsstring[]Groups to randomize when depth is CUSTOM (e.g. ["ARENA", "TRAILS", "BALL_COLOR"]).
seedstring1-64 characters of [a-zA-Z0-9_-] for deterministic reproduction.
metadataobjectUp to 20 keys of your own (strings up to 500 characters, numbers or booleans). Returned on every read of the job and in its webhooks, so you can match a video to your records.

Job response

HTTP 201 Created
{
  "id": "job-k3x92ab1c",
  "status": "queued",
  "duration": 30,
  "aspect_ratio": "9:16",
  "quality": "1080p60",
  "fps": 60,
  "resolution": "1080x1920",
  "mode": "GHOST_ESCAPE",
  "seed": "f81kq2z9",
  "credits_charged": 2,
  "credits_remaining": 8.5,
  "metadata": { "order_id": "A-1042" },
  "created_at": 1753159000
}

Poll for the result

GET https://ballengine.app/api/v1/videos/job-k3x92ab1c
Authorization: Bearer be_sk_...

Poll every 10 to 15 seconds until status is "done". When ready, download_url provides a direct 15-minute presigned MP4 link.

HTTP 200 OK
{
  "id": "job-k3x92ab1c",
  "status": "done",
  "duration": 30,
  "resolution": "1080x1920",
  "format": "mp4",
  "download_url": "https://pub-...r2.dev/renders/job-k3x92ab1c.mp4?...",
  "created_at": 1753159000
}

Need a fresh link later? GET /api/v1/videos/:id/download answers with a redirect to a new 15-minute link for as long as the video is kept (7 days).

List, page and cancel

GET /api/v1/videos lists the jobs created with your key, newest first, 20 per page by default (up to 100 with limit). When has_more is true, pass next_starting_after as starting_after to get the next page. Filter with status (queued, rendering, done, failed), created_after and created_before (Unix seconds).

GET https://ballengine.app/api/v1/videos?limit=50&status=done&starting_after=job-k3x92ab1c

HTTP 200 OK
{ "jobs": [ ... ], "has_more": true, "next_starting_after": "job-p0q8m2x4a" }

DELETE /api/v1/videos/:id cancels a queued job and refunds its credits in full. A job that has started rendering cannot be canceled (409 already_rendering); delete it once it has finished. Deleting a finished job removes the video file and the job.

DELETE https://ballengine.app/api/v1/videos/job-k3x92ab1c

HTTP 200 OK
{ "id": "job-k3x92ab1c", "deleted": true, "canceled": true, "credits_refunded": 1 }

Webhooks: get notified instead of polling

Register an https URL on your Developer page or through the API. When a render finishes we send it a signed POST with the result. Any 2xx answer within 10 seconds counts as delivered. Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours. An endpoint that fails 20 times in a row is switched off until you turn it back on.

POST https://ballengine.app/api/v1/webhooks
Authorization: Bearer be_sk_...

{ "url": "https://your-server.com/ballengine/webhook", "events": ["video.completed", "video.failed"] }

HTTP 201 Created
{ "id": "we_3f9c...", "url": "...", "events": [...], "status": "active", "secret": "whsec_..." }

The secret is returned once. Other calls: GET /api/v1/webhooks, DELETE /api/v1/webhooks/:id, and POST /api/v1/webhooks/:id/test to send a test event.

Limits and automatic switch-off

  • Test events: 3 per minute and 15 per hour per account. Resends: 5 per minute and 30 per hour. Adding endpoints: 5 per minute and 20 per hour. Going over answers 429 with retry_after.
  • An endpoint is switched off after 20 failed deliveries in a row, or after more than 200 deliveries in one hour.
  • If test or resend calls keep coming after the limit (10 refused calls in an hour), every endpoint on the account is switched off.
  • A switched-off endpoint receives nothing, test events included. Only you can turn it back on, by signing in to your Developer page. There is no API call for it, so a runaway script cannot undo its own switch-off.

What we send

POST https://your-server.com/ballengine/webhook
BallEngine-Event-Id: evt_job-k3x92ab1c_completed
BallEngine-Event-Type: video.completed
BallEngine-Signature: t=1753159120,v1=5d41402abc4b2a76b9719d911017c592...

{
  "id": "evt_job-k3x92ab1c_completed",
  "type": "video.completed",
  "api_version": "v1",
  "created_at": 1753159120,
  "data": {
    "video": {
      "id": "job-k3x92ab1c",
      "status": "done",
      "duration": 30,
      "resolution": "1080x1920",
      "format": "mp4",
      "seed": "f81kq2z9",
      "metadata": { "order_id": "A-1042" },
      "download_url": "https://ballengine.app/api/v1/videos/job-k3x92ab1c/download",
      "created_at": 1753159000,
      "finished_at": 1753159118
    }
  }
}

A video.failed event carries "status": "failed", an "error" message and "credits_refunded" instead of a download link. The same event can arrive more than once, so use its id to ignore repeats. In the rare case a render reported as failed later finishes, you get a video.completed after the video.failed, and the later one is correct.

Check the signature (Node.js)

import crypto from 'node:crypto';

// rawBody: the request body exactly as received, before JSON parsing.
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto.createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 || '');
  return fresh && a.length === b.length && crypto.timingSafeEqual(a, b);
}

Connect Claude, ChatGPT or your own agent

Claude, ChatGPT, Cursor or your own agent can create videos for you. There are three ways to connect them:

  1. 1.

    Copy and paste a prompt

    Nothing to install. Copy the prompt from your Developer dashboard into Claude.ai or ChatGPT.

  2. 2.

    OpenAPI 3.1 spec

    Paste https://ballengine.app/api/v1/openapi.json into Custom GPTs, Make.com, Zapier, or LangChain.

  3. 3.

    Hosted remote MCP

    Point Claude Desktop or Cursor at https://ballengine.app/api/v1/mcp. Nothing to install locally.

Example Claude Desktop configuration (claude_desktop_config.json):

{
  "mcpServers": {
    "ballengine": {
      "command": "npx",
      "args": ["-y", "@ballengine/mcp-server"],
      "env": {
        "BALLENGINE_API_KEY": "be_sk_YOUR_API_KEY"
      }
    }
  }
}

Example prompts:

  • "Claude, create a 30s 9:16 TikTok video with a neon cyberpunk theme, dark phonk music, and Ghost Escape mode."
  • "Generate a 15s square video for Instagram with Space Spiral mode and a gold luxury theme."
  • "Check the render status of job-k3x92ab1c."

Error codes

HTTPCodeMeaning
400invalid_requestInvalid duration, aspect ratio, mode, or JSON schema.
401invalid_api_keyMissing, malformed, unknown, or revoked key.
402insufficient_creditsBalance too low. Buy credits on your Developer page.
403api_access_disabledYour account does not have API access enabled.
404not_foundNo such job or webhook endpoint for this account.
409not_readyThe video has no file to download yet. Check its status first.
409already_renderingThe video is rendering and cannot be canceled. Delete it once it has finished.
409endpoint_disabledThe webhook endpoint is switched off. Turn it back on from your Developer page.
429rate_limited / concurrency_limitToo many requests (max 5 in-flight per key).
503capacity / unavailableQueue temporarily saturated, retry with exponential backoff.