OverviewAuthenticationScopesEndpointsVideo previewErrors & limitsPluginsBuild a platform
Documentation

Endpoints

27 endpoints under https://api.vidmoat.com/v1. Every one needs a bearer token; the scope column is what the credential must carry.
Copy any block below, set VIDMOAT_KEY, and it runs. Use a test key while you are exploring — it never spends credits and never queues a real render. Every response is { data… } on success and { error: { code, message, docs_url } } on failure; see errors.

Account

Who the credential belongs to, what it may do, and what it has spent.
GET/v1/me
account.read ·
The owner of this credential: plan, credit balance, and the scopes this key actually carries. The first call to make when a request is 403-ing and you are not sure why.
curl https://api.vidmoat.com/v1/me \
  -H "Authorization: Bearer $VIDMOAT_KEY"
GET/v1/usage
account.read ·
Request counts (with the 4xx/5xx split) and credits spent, for building your own dashboard or a spend alarm.
curl "https://api.vidmoat.com/v1/usage?days=30" \
  -H "Authorization: Bearer $VIDMOAT_KEY"
GET/v1/schema/commands
no scope ·
The full command vocabulary the editor accepts, verbatim from COMMAND_SCHEMA. Unauthenticated, because it is a description of the format rather than of your data.
curl https://api.vidmoat.com/v1/schema/commands
This is the same schema the editor and the MCP server validate against. If an op is not in here, it does not exist.

Projects

A project is a timeline document. Every mutation goes through the same command reducer the editor uses, so there is exactly one definition of what an edit means.
GET/v1/projects
projects.read ·
List your projects, newest first.
curl "https://api.vidmoat.com/v1/projects?limit=20" \
  -H "Authorization: Bearer $VIDMOAT_KEY"
POST/v1/projects
projects.write · project quota
Create an empty project. Counts against the plan project limit.
curl -X POST https://api.vidmoat.com/v1/projects \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"My first API edit"}'
GET/v1/projects/{id}
projects.read ·
One project including its full edit document — clips, tracks, settings.
curl https://api.vidmoat.com/v1/projects/$ID \
  -H "Authorization: Bearer $VIDMOAT_KEY"
PATCH/v1/projects/{id}
projects.write ·
Rename, or change output settings such as resolution and frame rate.
curl -X PATCH https://api.vidmoat.com/v1/projects/$ID \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Renamed"}'
DELETE/v1/projects/{id}
projects.write ·
Delete a project and its render jobs.
curl -X DELETE https://api.vidmoat.com/v1/projects/$ID \
  -H "Authorization: Bearer $VIDMOAT_KEY"
POST/v1/projects/{id}/commands
projects.write · plan-gated ops
Apply an ordered list of edit commands. This is the only way to change a timeline, and it is the same reducer the editor calls — anything you can do by hand you can do here.
curl -X POST https://api.vidmoat.com/v1/projects/$ID/commands \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"commands":[
        {"op":"addTextClip","text":"Hello","start":0,"duration":3},
        {"op":"addAdjustmentLayer"},
        {"op":"setColor","saturation":1.1}
      ]}'
Commands apply atomically: if one fails validation, none are written. Individual ops can still be plan-gated — a pro effect on a free plan returns 402 while the rest of the batch is rolled back.
POST/v1/projects/{id}/validate
projects.read ·
Dry-run a command batch and get back the lint warnings without persisting anything. Overlapping text, off-canvas elements, unreadable font sizes.
curl -X POST https://api.vidmoat.com/v1/projects/$ID/validate \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"commands":[{"op":"addTextClip","text":"Hi","start":0,"duration":2}]}'
Read the lint. Numeric x/y/fontSize choices routinely overlap on the real canvas, and this is cheaper than a render to find out.

Rendering

Renders are queued, not synchronous. A 5-second clip takes seconds; a three-minute one takes minutes.
POST/v1/renders
render.write · export quota
Queue an export. Resolution cap, watermark and concurrency come from the owner’s plan.
curl -X POST https://api.vidmoat.com/v1/renders \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"'$ID'","format":"mp4","quality":"standard"}'
GET/v1/renders
render.read ·
List your render jobs, newest first. Filter by `projectId` or `status`, and page with `cursor`.
curl "https://api.vidmoat.com/v1/renders?limit=20&status=completed" \
  -H "Authorization: Bearer $VIDMOAT_KEY"
GET/v1/renders/{id}
render.read ·
Poll a job: PENDING → PROCESSING → COMPLETED or FAILED, with progress 0-100 and the download URL on completion.
curl https://api.vidmoat.com/v1/renders/$JOB_ID \
  -H "Authorization: Bearer $VIDMOAT_KEY"
Poll every few seconds, not every few hundred milliseconds — the rate limiter does not distinguish an eager poller from an attack. render.completed webhooks land in Phase 3 and are the right answer.
GET/v1/projects/{id}/preview
render.read ·
Three representations: metadata (default, launches no browser), the full scrubable composition HTML, or one rendered frame. See the video-preview page.
curl "https://api.vidmoat.com/v1/projects/$ID/preview" \
  -H "Authorization: Bearer $VIDMOAT_KEY"

# a real frame, as JPEG bytes
curl "https://api.vidmoat.com/v1/projects/$ID/preview?format=image&at=2.5" \
  -H "Authorization: Bearer $VIDMOAT_KEY" -o frame.jpg
format=json is free and starts no browser. format=image launches Chromium and carries its own 10/min limit on top of your plan rate.

Media

Bring footage in. Everything you upload is attributed to the app owner’s account, and the usual moderation applies.
GET/v1/media
media.read ·
List the media on the account, with the URLs a clip src can point at.
curl https://api.vidmoat.com/v1/media \
  -H "Authorization: Bearer $VIDMOAT_KEY"
POST/v1/media/uploads
media.write · upload cap
Upload a file directly, multipart.
curl -X POST https://api.vidmoat.com/v1/media/uploads \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -F "file=@clip.mp4"
POST/v1/media/import
media.write · upload cap
Fetch a public http(s) URL server-side. Destinations pass through the SSRF guard, so private ranges and metadata endpoints are refused.
curl -X POST https://api.vidmoat.com/v1/media/import \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/clip.mp4"}'
GET/v1/stock/search
stock.read ·
Search the stock library by keyword, then import a result’s downloadUrl with /v1/media/import.
curl "https://api.vidmoat.com/v1/stock/search?query=city+at+night&type=video" \
  -H "Authorization: Bearer $VIDMOAT_KEY"

Generation

These spend credits, and they are the reason scopes exist. Every one runs the prompt-safety check; refusals are a 400 with a reason, not a silent empty result.
POST/v1/ai/transcriptions
ai.transcribe · 10 cr / 5 min
Word-level timings for captions. Feed words[] straight into an addCaptions command.
curl -X POST https://api.vidmoat.com/v1/ai/transcriptions \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"src":"https://example.com/voice.mp3","language":"en"}'
Priced per 5 minutes of media, rounded up, and the length is measured before any work starts — anything over 30 minutes is refused with a 400 rather than transcribed and billed. Passing `script` + `duration` instead of `src` needs no provider and is free.
POST/v1/ai/speech
ai.speech · 15 credits
Text to speech. Input is capped at 1,000 characters, which is what keeps a flat price honest.
curl -X POST https://api.vidmoat.com/v1/ai/speech \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"Ship it.","voice":"nova"}'
POST/v1/ai/images
ai.image · 60 credits
Generate a still.
curl -X POST https://api.vidmoat.com/v1/ai/images \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"a lighthouse in fog, cinematic"}'
POST/v1/ai/stickers
ai.image · 20 credits
Generate a cut-out sticker with a transparent background.
curl -X POST https://api.vidmoat.com/v1/ai/stickers \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"a winking crab"}'
POST/v1/ai/videos
ai.video · 80 credits/second (400 for the 5s default)
Generate a clip. The dearest action on the platform, and Studio-only.
curl -X POST https://api.vidmoat.com/v1/ai/videos \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"drone over a harbour at dawn","durationSec":5}'
Priced per second, not flat: 80 credits/second on grok-imagine-video (128 on 1.5), so 5s costs 400 and the 15s maximum costs 1,200. `durationSec` is clamped to 1-15 and the SAME clamped number is both sent to the provider and billed.
GET/v1/ai/jobs/{id}
matching ai.* ·
Poll a generation job. Same shape for every generator.
curl https://api.vidmoat.com/v1/ai/jobs/$JOB_ID \
  -H "Authorization: Bearer $VIDMOAT_KEY"
POST/v1/ai/agent
ai.agent · 5 credits
Describe an edit in `message` and let the agent emit and apply the commands. One turn per call, charged once — you drive the loop.
curl -X POST https://api.vidmoat.com/v1/ai/agent \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"'$ID'","message":"cut the silences and add captions"}'
Add `dryRun: true` to get the plan back without applying it — the credit is still spent, because the model call is what it pays for.
POST/v1/ai/analyze
ai.analyze · per action
NOT LIVE YET. Scene, audio and voice-activity analysis over a media URL — what to feed a cut decision.
curl -X POST https://api.vidmoat.com/v1/ai/analyze \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"src":"https://example.com/clip.mp4","kind":"visual"}'
Not shipped in this release. The scope exists and the analysis runs internally, but the public endpoint is not exposed, so this path returns 404 today. Documented so you can design against the shape.

Webhooks

Phase 3. Documented here so you can design against the shape now; the endpoints are not live yet.
GET · POST · DELETE/v1/webhooks[/{id}]
webhooks.manage ·
Register a destination for render.completed, generation.completed, credits.low and friends. Signed Stripe-style with a timestamped HMAC prefix so a replayed body is detectable.
# not yet live
curl -X POST https://api.vidmoat.com/v1/webhooks \
  -H "Authorization: Bearer $VIDMOAT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/vidmoat","events":["render.completed"]}'
Until this ships, poll GET /v1/renders/{id}. Destination URLs will pass through the same SSRF guard as media import.

Idempotency and retries

GET is safe to retry freely. POST /v1/projects/{id}/commands is not idempotent — a retried batch appends the clips again — so treat a timeout as unknown and re-read the project before deciding. POST /v1/renders re-queues, which spends another export from the quota. An Idempotency-Key header is on the roadmap; until it exists, guard mutations on your side.

A note on polling

# right: back off, and stop
for i in $(seq 1 120); do
  s=$(curl -s https://api.vidmoat.com/v1/renders/$JOB \
    -H "Authorization: Bearer $VIDMOAT_KEY" | jq -r .render.status)
  [ "$s" = "COMPLETED" ] && break
  [ "$s" = "FAILED" ] && { echo "render failed"; exit 1; }
  sleep 5
done
The rate limiter cannot tell an eager poller from an attack, and a tight loop against a three-minute render will exhaust your budget before the video exists. Five seconds is plenty. Webhooks (Phase 3) remove the loop entirely.
Versioning. Additive changes ship into v1 — new fields, new endpoints, new enum values. Ignore unknown fields; a client that rejects them will break on a routine release. Breaking changes get a /v2, and v1 is supported for at least 12 months after one exists. Send Vidmoat-Version: 2026-08-01 to pin date-based behaviour within v1.