1313 sections — tap to jump
What you can build
Programmatically create video projects and talking-avatar clips with the same account, credits and job history as the web app.
Use REST from a backend, automation or product integration. Use MCP when an AI agent should inspect tools, estimate cost and submit a render itself.
The public v1 surface is asynchronous: submit a job, keep its id, then poll or receive a signed webhook. Paid generation requires an eligible VlogMe plan.
- Estimate before spending credits.
- Generate a portrait-based video from text and a voice, or from uploaded audio.
- Read progress and receive a temporary signed download URL.
- Use idempotency keys to make paid retries safe.
Run VlogMe Avatar on Replicate
For a simple image-plus-audio endpoint, the public VlogMe Avatar bridge is also available as a hosted Replicate model.
Use this route when Replicate already handles your infrastructure and billing. The direct VlogMe API remains the complete integration surface for credits, job history, REST and MCP.
The hosted bridge returns a vertical talking-avatar MP4 and keeps subtitles enabled by default.
Authentication
Send a Bearer token with every protected request. A token is displayed only once, so store it in a secret manager.
Create the token in Settings → API and never expose it in browser code, mobile bundles, public repositories or client logs.
The REST base URL is https://vlogme.ai/api/public/v1. Rotate a token if it may have leaked.
Authorization: Bearer vlm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxhttps://dev.vlogme.ai/api/public/v1Quickstart
Create a token, estimate the request, submit a render and poll until the status becomes terminal.
The example sends a portrait URL, script, voice id and aspect ratio. Replace all placeholders with assets your server can fetch.
A successful POST returns 202 Accepted immediately. Keep polling roughly every ten seconds, or pass a webhook_url.
curl -X POST https://dev.vlogme.ai/api/public/v1/videos \
-H "Authorization: Bearer $VLOGME_TOKEN" \
-H "Idempotency-Key: video-request-001" \
-H "Content-Type: application/json" \
-d '{
"portrait_url": "https://example.com/portrait.jpg",
"aspect_ratio": "16:9",
"script": "Welcome to the demo.",
"voice_id": "EXAVITQu4vr4xnSDxMaL",
"webhook_url": "https://yourapp.com/webhooks/vlogme"
}'REST endpoints
A compact JSON API with OpenAPI 3.1, stable error codes and rate-limit headers.
The machine-readable specification is available at /api/public/v1/openapi.json. Generate a typed client or import it into Postman or Insomnia.
Every response carries X-Request-Id. Async POST /videos also returns Location with the polling URL.
/meUser id, plan and credit balance/voicesVoice IDs available for synthesis/videosStart an asynchronous render/videos/:idRead status and signed download URL/videosList recent renders with pagination/videos/estimateEstimate credits without charging/videos/:idDelete or cancel an eligible render/healthLiveness probe; authentication not requiredcurl https://dev.vlogme.ai/api/public/v1/videos/$VIDEO_ID \
-H "Authorization: Bearer $VLOGME_TOKEN"Create an AI video
Provide a portrait and either script plus voice_id or an audio asset. Rendering continues asynchronously.
Use portrait_url or portrait_base64. For speech, send script with voice_id, or audio_url/audio_base64. Optional fields include aspect_ratio, emotion_preset, live_subtitles, title and webhook_url.
Every paid POST requires a unique Idempotency-Key. Retrying with the same key returns the original render instead of charging twice.
- Supported aspect ratios: 9:16 by default, 16:9 and 1:1.
- Top-level inserts can add overlay or cut b-roll.
- audio_mode can use auto, prompt, asset or off for project background audio.
- The 202 response includes id, status, credits_charged, estimated_seconds and warnings.
curl -X POST https://dev.vlogme.ai/api/public/v1/videos \
-H "Authorization: Bearer $VLOGME_TOKEN" \
-H "Idempotency-Key: video-request-001" \
-H "Content-Type: application/json" \
-d '{
"portrait_url": "https://example.com/portrait.jpg",
"aspect_ratio": "16:9",
"script": "Welcome to the demo.",
"voice_id": "EXAVITQu4vr4xnSDxMaL",
"webhook_url": "https://yourapp.com/webhooks/vlogme"
}'Script grammar
The machine-readable DSL describes scene switches, b-roll, audio inserts, pauses and inline voice-performance tags.
Use @imageN to switch the speaking scene, curly-brace inserts for overlay or chain b-roll, @audioN for audio assets and ElevenLabs performance tags such as [shocked].
The downloadable specification is intentionally language-neutral for tools and LLMs. MCP clients can read the same contract with script_grammar_help.
SCRIPT-GRAMMAR.md
# VlogMe Script Grammar — canonical contract
> Single source of truth for two contracts:
>
> **A. Frontend ↔ Backend (script grammar)** — what humans and AI write.
> **B. Backend ↔ Worker (payload)** — the structured JSON the render fleet consumes.
>
> The frontend never builds a worker payload directly. `flatToPayloads()` in
> `src/lib/scenes/flat-script.ts` is the single bridge between A and B.
---
## A. Script grammar (frontend ↔ backend)
A VlogMe script is plain text driving a short-form render. The project-level
`aspect_ratio` decides the frame (`9:16`, `16:9`, or `1:1`); the script grammar
is identical for all three formats. There are **four explicit beat forms**, plus
a plain-text continuation line for the current avatar. The shape of the token
decides the kind — never the card role.
| # | Form | Meaning |
| --- | ---------------------- | ---------------------------------------------------------------------- |
| 1 | `@imageN <text>` | Avatar speaks `<text>`, anchored to photo slot N. |
| 2 | `@imageN { <prompt> }` | Standalone video clip generated from photo N. Prompt is in the braces. |
| 3 | `{ @imageM <prompt> }` | Overlay clip on top of the current avatar, using photo M as 1st frame. |
| 4 | `{ <prompt> }` | Continue — extends the previous clip, no reference image of its own. |
Plus one media token: `@audioN` on its own line drops audio clip N on the
current avatar.
Plain text on its own line continues speech for the current avatar. This is
mainly used after an overlay beat:
```text
@image1 So I open the laptop.
{ @image3 slow push-in on the glowing keyboard }:3
And the cursor starts moving on its own.
```
### Rules
- Whitespace, blank lines and indentation are **insignificant** — only the
token shape matters.
- Avatar text (form 1) preserves its newlines verbatim into the worker
payload's `script` field. Multi-line dialogue stays multi-line end-to-end.
- The current avatar = the most recent avatar speech tag. Forms 3 and 4 anchor
to that avatar/beat.
- Form 4 requires a preceding rendered frame/current avatar to continue from.
- Form 3 starts directly with `@imageN`: `{ @image3 fade out }`.
- In form 4, the optional keyword `continue` is stripped:
`{ continue sparks fly }` works the same as `{ sparks fly }`.
- `@imageN-K` (K in 0..100): cross-fade transition code applied when the
avatar switches (form 1 only).
### Duration override
Any form 2 / 3 / 4 may carry an explicit clip duration in seconds:
- `{ ... }:D` — after the closing brace.
- `@imageN { ... }:D` — same.
Without `:D` the clip uses the system default (3s), capped at 5s.
### Brace body options
Forms 2 / 3 / 4 can use either a plain prompt or an advanced pipe body:
```text
@image2 { slow push-in on a mountain lake }:3
{ @image3 v:glowing OPEN sign, slow push-in | n:watermark, text | s:soft city hum | t5 }:3
{ v:camera keeps drifting upward | am:off }:2
```
Supported segments:
- `v:<visual prompt>` — main visual prompt.
- `n:<negative prompt>` — visual negative prompt.
- `s:<sound prompt>` / `an:<negative sound>` — generated audio prompt fields.
- `@audioM` — uploaded audio clip under this video beat.
- `am:auto|prompt|asset|off` — audio mode.
- `ag:<db>` — audio gain.
- `tK` — transition code, 0..100.
### Examples
```text
@image1 Hey, let me show you something.
@image1 So I open the laptop.
{ @image3 slow push-in on the glowing keyboard }:3
And the cursor starts moving on its own.
@image2 { dolphin breaches in slow motion against the sunrise }:4
{ camera keeps drifting up, sky turns deeper orange }:2
@image1 [shocked] What the hell is happening?
@audio1
```
Breakdown:
- Line 1 — avatar 1 speaks one sentence.
- Line 3 — overlay over the current avatar using photo 3.
- Line 4 — avatar 1 continues speaking.
- Line 6 — standalone video from photo 2 (4s).
- Line 7 — continue clip extending the previous shot (2s, no reference photo).
- Line 9 — avatar 1 again, with an ElevenLabs `[shocked]` audio tag.
- Line 10 — drops audio clip 1 on top of the avatar.
### Legacy back-compat
The old "bare `@imageN` lets the card's role decide" form is still accepted
by the parser. Specifically: an `@imageN` line **without** braces falls back
to `card.role` + `card.effectInsertMode` to pick avatar / video / overlay /
continue. This keeps the timeline editor — which currently edits cards in
the bare form — working unchanged while we migrate the UI to render explicit
brace cards.
When AI generates a script, it should prefer the **four explicit forms**.
Parser/serializer round-trips currently normalize back to the legacy bare form
for the timeline UI; that will change in a follow-up.
### Forbidden
- Mid-line `@imageN` outside of a brace. Each tag is on its own line OR
inside a `{ … }` block.
- Inline overlay inside avatar speech, e.g. `@image1 words {@image2 ...}:3`.
- Bare `@imageN` video instructions from new AI output. Generate
`@imageN { ... }:D` instead.
- Nested braces.
- Anything else not listed in §A.
---
## B. Worker payload (backend ↔ worker)
The worker consumes `ScenePayload[]` (defined in `src/lib/scenes/scenes.ts`).
The frontend never builds these — `flatToPayloads()` is the only producer.
Each scene has an `input_type` discriminator:
| `input_type` | Comes from script form | Key fields |
| --------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `text` | Form 1 | `image_path`, `script` (avatar speech), `voice_id`, `transition` |
| `audio` | `@audioN` | `image_path` (current avatar), `audio_path`, `audio_duration_seconds` |
| `ltx` | Form 2 | `image_path` (source photo), `script` (visual prompt), `audio_duration_seconds` |
| `video_overlay` | Form 3 | `image_path` (anchor avatar), `overlay_source_image_path` (photo M), `overlay_visual_prompt`, `overlay_min_duration_seconds` |
| `video_chain` | Form 4 | `chain_from_previous: true`, `overlay_source_image_path`, `overlay_visual_prompt` |
Insert variants (overlay / cut) for nested timing inside an avatar audio
chunk follow `docs/public-api-inserts-contract.md`. That doc is the contract
between the frontend builder pipeline and the public render API.
The worker payload shape is **stable** — adding new script grammar features
must NOT require worker changes. Map them in `flatToPayloads()` first.
---
## Pipeline (who owns what)
```text
WishesField ← user types brief + uploads media
│
▼
buildScriptFromBrief (server) ← Gemini generates canonical script
│
▼
useFlatScriptBridge ← parses script, mirrors timeline ↔ Code
│
▼
TimelineBlocks ← user-editable cards
│
▼
flatToPayloads() ← THE bridge: script → ScenePayload[]
│
▼
prepare-render (server) ← per-scene asset staging, validation
│
▼
render fleet workers ← Hunyuan / overlay / avatar
```
**Key principle**: the script grammar is the _only_ thing the user (or AI)
authors. Everything downstream — payload shape, worker inputs, asset paths,
voice IDs, transition codes — is derived. Adding new script behavior =
extend `flatToPayloads()`; don't ask AI or users to write payload JSON.
Webhooks
Pass webhook_url and VlogMe sends one completion or failure event, with retries when delivery is transiently unavailable.
Verify X-Vlogme-Signature against timestamp + raw_body using the dedicated whsec_ signing secret from Settings → API. Reject timestamps older than five minutes and deduplicate on X-Vlogme-Event-Id.
Return any 2xx within five seconds. Network errors and 5xx responses are retried with backoff; 4xx is treated as a permanent rejection. A delayed event may contain an expired URL, so GET the video again for a fresh signed link.
ts = request.headers["X-Vlogme-Timestamp"]
secret = "whsec_..." # Settings -> API
expected = "sha256=" + hmac_sha256(secret, ts + "." + raw_body).hex()
assert constant_time_eq(expected, request.headers["X-Vlogme-Signature"])
assert abs(now() - int(ts)) < 300MCP server
Connect an AI agent through native Streamable HTTP using a VlogMe token or interactive OAuth.
The MCP tools wrap REST v1 and share its data shapes, credit formula and stable error codes. Changes are additive: clients should ignore unknown fields.
Generation tools cover voices, balance, estimates, portraits, job submission, status, cancellation and history. Authorized internal accounts may also use the work-item tools.
POST https://dev.vlogme.ai/api/mcpscript_grammar_helplist_voicesget_balanceestimate_creditslist_portraitsgenerate_videoget_videocancel_videolist_my_videoslist_bugsget_bugreport_bugupdate_bug_statusClient setup
Claude Code, Cursor and Codex support Streamable HTTP. Older stdio-only clients can use mcp-remote.
Choose exactly one authentication mode: interactive OAuth or an API token environment variable. Do not combine them.
After changing an MCP configuration, start a new client session so it discovers the tools again.
# OAuth
codex mcp add vlogme --url https://dev.vlogme.ai/api/mcp
codex mcp login vlogme --scopes mcp:full,mcp:work_items
# or API token
export VLOGME_TOKEN=vlm_live_xxxxxxxxxxxx
codex mcp add vlogme --url https://dev.vlogme.ai/api/mcp --bearer-token-env-var VLOGME_TOKEN{
"mcpServers": {
"vlogme": {
"url": "https://dev.vlogme.ai/api/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN_HERE" }
}
}
}End-to-end agent example
A natural-language request becomes a sequence of tool calls that remains visible and auditable.
An agent can list voices, estimate the requested render, ask for approval, call generate_video and monitor get_video until a terminal status.
The agent still needs an accessible portrait, a valid plan and sufficient credits. Per-plan rate limits apply to MCP and REST equally.
Create a 16:9 talking-avatar video from this portrait.
Use a warm, natural voice. Estimate the credits first and ask before generating.
After approval, monitor the job and return the final download URL.Agent skill
Give an agent a short operating policy so it estimates cost, requests approval and handles asynchronous status correctly.
The skill should tell the agent when VlogMe is appropriate, which tools to call, how to preserve idempotency and when to stop polling.
Never place a live token inside the skill file. Keep credentials in the client environment or OAuth store.
---
name: vlogme-video
description: Estimate and generate VlogMe video jobs through MCP.
---
1. Validate the portrait and requested format.
2. Call estimate_credits before a paid generation.
3. Ask for approval with the estimate.
4. Use a stable idempotency key for transport retries.
5. Poll get_video until a terminal status.Errors and retries
Errors use { error: { code, message } }. Branch on the stable code rather than the translated human-readable message.
Use X-Request-Id when contacting support. A 429 includes Retry-After; authentication and validation errors should be corrected rather than blindly retried.
The OpenAPI document contains the complete schemas and error list. Successful responses expose rate-limit limit, remaining and reset headers.
missing_token401{ error: { code: "missing_token", message } }invalid_token401{ error: { code: "invalid_token", message } }token_expired401{ error: { code: "token_expired", message } }plan_required403{ error: { code: "plan_required", message } }insufficient_credits402{ error: { code: "insufficient_credits", message } }invalid_input400{ error: { code: "invalid_input", message } }invalid_asset400{ error: { code: "invalid_asset", message } }invalid_json400{ error: { code: "invalid_json", message } }not_found404{ error: { code: "not_found", message } }method_not_allowed405{ error: { code: "method_not_allowed", message } }already_started409{ error: { code: "already_started", message } }billing_conflict409{ error: { code: "billing_conflict", message } }rate_limited429{ error: { code: "rate_limited", message } }internal_error500{ error: { code: "internal_error", message } }