Whiteboard Video API

Turn your own figures into narrated whiteboard videos from your code, n8n, Make or an AI agent. The voice reads your own words, or morphedra composes the narration from your figures and notes.

Quick start

  1. Sign in and open API keys. Create a test key (free: 3 test videos of up to 15 seconds, with a watermark).
  2. Send a request. The call returns at once with status queued.
  3. Wait for the webhook, or ask for the video every 5 to 10 seconds until its status is completed.
curl -X POST https://api.morphedra.com/v1/videos \
  -H "Authorization: Bearer $MORPHEDRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-first-video-001" \
  -d '{
    "figures": [
      {
        "image_url": "https://example.com/figure1.png",
        "script": "Cells take up the signal and then switch on the repair genes."
      }
    ],
    "max_duration_seconds": 30,
    "webhook_url": "https://example.com/hooks/morphedra"
  }'

The answer (HTTP 202):

{
  "id": "vid_pTcL5JlR5yGALqil",
  "object": "video",
  "status": "queued",
  "progress": 0,
  "stage": "Queued",
  "test": false,
  "aspect": "landscape",
  "title": "",
  "max_duration_seconds": 30,
  "duration_seconds": null,
  "video_url": null,
  "thumbnail_url": null,
  "transcript": null,
  "cost": { "currency": "usd", "reserved_cents": 90, "charged_cents": 0 },
  "error": null,
  "created_at": "2026-11-02T06:01:45.051Z",
  "finished_at": null
}

Call the API from a server, never from a web page: keys must stay secret, and browsers are not allowed to call it from other websites.

Authentication

Send your key in the header Authorization: Bearer mk_live_.... Test keys start with mk_test_. Keys in the URL are refused. You can have up to 5 active keys and revoke any key at any time on the developer page. Each key has its own webhook signing secret.

Every response carries an X-Request-Id header. Quote it if you contact support.

Create a video: POST /v1/videos

FieldUseMeaning
figuresrequired1 to 6 figures, in the order they are drawn. Each is an object (see below).
figures[].image_urlone of the twoA public https link to a PNG, JPG or WEBP image of at most 5 MB. We download it and keep our own copy.
figures[].image_base64one of the twoThe image as base64 (a data: prefix is accepted). At most 5 MB per image.
figures[].scriptoptionalThe exact words the voice reads for this figure. Give a script for every figure, or for none.
notesoptionalBackground for the narration. Used only when no scripts are given. Up to 5,000 characters.
voiceoptionalCharon (default), Orus, Kore or Aoede.
titleoptionalYour own label, up to 100 characters.
max_duration_secondsoptionalWhole number from 10 to 600. Default 60. This is the most you can be charged for the video.
webhook_urloptionalA public https address. We send video.completed or video.failed to it.

Two ways to narrate

  • With scripts: the voice reads each script word for word. Nothing is added, dropped or reworded, and nothing is shortened. If the scripts cannot fit max_duration_seconds, the request is refused before anything is charged. We check this at about 65 words per minute.
  • Without scripts: the narration is composed from what is visible in your figures and your optional notes. It does not invent facts, numbers or names.

Figures and format

  • The video format follows your figures. All figures must be landscape or all portrait (taller than wide by more than 5 percent). Square images count as landscape. The response shows the detected aspect.
  • Accepted: PNG, JPG, WEBP, at most 5 MB each, at least 200 pixels on each side, shape between 2.5:1 and 1:2.5. Figures on a white background work best.
  • With 2 or more figures, the video ends with all figures shown together while the closing words are spoken.
  • Idempotency: send an Idempotency-Key header (up to 64 characters: letters, digits, - and _). Repeating the same request with the same key returns the first video instead of making a second one. The same key with a different request returns 409.
  • Base64 uploads are limited to about 7 MB in total per request. For larger figures, use image_url.

Get, list and cancel

  • GET /v1/videos/{id}: the video object. Statuses: queued, processing, completed, failed, cancelled.
  • GET /v1/videos?limit=20&starting_after=vid_...: your videos, newest first (limit up to 50).
  • POST /v1/videos/{id}/cancel: works only while the video is queued. The full hold is returned.
  • GET /v1/balance: your balance in cents and your test-video allowance.
  • GET /v1/usage?days=30: totals and counts by status (up to 90 days).
curl https://api.morphedra.com/v1/videos/vid_pTcL5JlR5yGALqil \
  -H "Authorization: Bearer $MORPHEDRA_API_KEY"

A finished video:

{
  "id": "vid_pTcL5JlR5yGALqil",
  "object": "video",
  "status": "completed",
  "progress": 100,
  "stage": "Done",
  "test": false,
  "aspect": "landscape",
  "max_duration_seconds": 30,
  "duration_seconds": 11.4,
  "video_url": "https://media.octapost.ai/whiteboard-videos/....mp4",
  "thumbnail_url": "https://media.octapost.ai/whiteboard-thumbs/....jpg",
  "transcript": "Cells take up the signal and then switch on the repair genes.",
  "cost": { "currency": "usd", "reserved_cents": 90, "charged_cents": 34 },
  "error": null,
  "created_at": "2026-11-02T06:01:45.051Z",
  "finished_at": "2026-11-02T06:02:39.913Z"
}

Polling counts toward the limit of 30 requests per minute per key. Poll every 5 to 10 seconds, or use a webhook instead. A video usually takes one to six minutes, depending on its length.

Webhooks

Set webhook_url to receive video.completed or video.failed as a POST with a JSON body. The id of the event is stable, so use it to ignore repeats.

{
  "id": "evt_vid_pTcL5JlR5yGALqil",
  "type": "video.completed",
  "created": 1793603748,
  "data": { "id": "vid_pTcL5JlR5yGALqil", "object": "video", "status": "completed", "...": "same object as GET /v1/videos/{id}" }
}

Headers: morphedra-Event and morphedra-Signature: t=<unix seconds>,v1=<hex>. The signature is an HMAC-SHA256, with your key's webhook secret, of the text t + "." + raw body. Check it, and reject events whose time is more than 5 minutes old.

import crypto from "node:crypto";

// secret: the webhook signing secret shown next to your key. rawBody: the request body exactly as received (a string).
export function verifymorphedra(secret, rawBody, header, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac("sha256", secret).update(parts.t + "." + rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
import hmac, hashlib, time

def verify_morphedra(secret: str, raw_body: bytes, header: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts["t"])
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), (parts["t"] + ".").encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Answer with any 2xx status within 10 seconds. If we get no 2xx, we retry after 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours (6 attempts in all). The address must be a public https address. If you miss a webhook, the video is still available by polling for 30 days.

Pricing and balance

  • $1.80 per minute of finished video, billed per second (3 cents a second). The shortest charge is 10 seconds ($0.30).
  • When you submit, we hold max_duration_seconds x 3 cents from your balance. When the video is done, we charge its real length, never more than the hold, and release the rest.
  • A video that fails is not charged. Its hold returns to your balance. A queued video you cancel is also returned in full.
  • Balance is prepaid: add $20, $50 or $150 on the developer page. It is separate from in-app credits. Balance does not expire.
  • Test keys: 3 free videos of up to 15 seconds, with a watermark. They never touch your balance.

Limits

  • 30 requests per minute per key (header X-RateLimit-Remaining; Retry-After when limited).
  • 2 videos running at the same time per account.
  • Up to 6 figures, 5 MB each; videos up to 600 seconds.
  • Figure links must be public https links. Links to private or internal addresses are refused.

Examples

A complete example for Windows PowerShell. Use a test key while you try it, and replace the image link with a public https link to your own figure.

# 1. Keep your key in a variable for this PowerShell window only
$env:MORPHEDRA_API_KEY = Read-Host "Paste your key"

# 2. Create a video
$base = "https://api.morphedra.com/v1"
$h = @{ Authorization = "Bearer $env:MORPHEDRA_API_KEY"; "Idempotency-Key" = [guid]::NewGuid().ToString() }
$body = @{
  figures = @( @{ image_url = "https://example.com/figure1.png"; script = "Cells take up the signal and then switch on the repair genes." } )
  max_duration_seconds = 15
} | ConvertTo-Json -Depth 5
$video = Invoke-RestMethod -Method Post -Uri "$base/videos" -Headers $h -Body $body -ContentType "application/json"

# 3. Wait until it is finished
$h2 = @{ Authorization = "Bearer $env:MORPHEDRA_API_KEY" }
do {
  Start-Sleep -Seconds 8
  $video = Invoke-RestMethod -Uri "$base/videos/$($video.id)" -Headers $h2
  "$($video.status)  $($video.progress)%"
} while ($video.status -in @("queued", "processing"))
$video.video_url

n8n, Make, AI agents and other tools: any tool that can send a web request can use the API. Use its HTTP Request step to send POST /v1/videos with the header Authorization: Bearer your key and a JSON body as above, and set webhook_url to receive the finished video. The OpenAPI file describes every endpoint.

Errors

Every error has the same shape. The code is stable; the message is for people.

{
  "error": {
    "code": "script_too_long",
    "message": "Your scripts have 40 words, which needs about 37 seconds, but max_duration_seconds is 15. ...",
    "request_id": "req_df96e52f5e28341b",
    "words": 40,
    "estimated_seconds": 37,
    "max_duration_seconds": 15,
    "max_words": 16
  }
}
HTTPCodeMeaning
401missing_api_key / invalid_api_keyNo key in the Authorization header, or the key is wrong or revoked.
429rate_limitedMore than 30 requests per minute with this key. Wait the number of seconds in Retry-After.
429too_many_running_jobsYou already have 2 videos queued or processing.
402insufficient_balanceYour balance cannot cover the hold (max_duration_seconds x 3 cents).
403test_limit_reachedThe 3 free test videos are used up.
400invalid_figures, invalid_image, image_too_large, image_too_smallA figure is missing, unreadable, over 5 MB or under 200 pixels on a side.
400image_aspect_ratio_unsupportedThe image is wider than 2.5:1 or taller than 1:2.5.
400mixed_orientationFigures must be all landscape or all portrait.
400invalid_image_url / image_download_failedThe link is not a public https link, or the download failed.
400scripts_mismatchSome figures have a script and some do not.
400script_too_longThe scripts cannot fit max_duration_seconds. Nothing is shortened or charged.
400possible_personal_dataA script or the notes seem to contain personal identifiers. Remove them.
400invalid_voice / invalid_notes / invalid_max_duration / invalid_webhook_urlA field has an unsupported value.
400 / 409invalid_idempotency_key / idempotency_key_reusedThe Idempotency-Key is malformed, or was used with a different request.
404 / 409not_found / not_cancellableNo such video, or it is no longer queued.
500internal_errorA problem on our side. Nothing was charged. Try again.

A video that fails while processing has status failed with an error.code such as figure_not_drawable, narration_failed, processing_failed, timeout or queue_timeout. You are not charged.

Data and retention

  • Do not send personal health information, patient data or other personal identifiers in figures, scripts or notes. See the API terms and the acceptable use policy.
  • We keep our own copy of your figures. Video records stay in the API for 30 days; the video, thumbnail and figure files are deleted after 90 days. Download what you need to keep.
  • Videos made with live keys have no watermark. Test videos carry a watermark and are for evaluation.

Questions: support@morphedra.com. Machine-readable description: openapi.json and llms.txt.