WWevi Developers

REST API Reference

Endpoints, authentication headers, request payloads, and status codes.

Wevi Developer REST API Reference

Base API Endpoint:

https://api-v2.wevi.ai/api/v2

Every response is wrapped as { "success": true, "data": …, "meta": { … } }.


Authentication

Pass your Wevi API Key in either the X-API-Key or Authorization: Bearer header:

X-API-Key: wevi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

or

Authorization: Bearer wevi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

API-key callers only ever see published, active templates. wevi_test_ keys run in sandbox mode: watermarked renders, 720p cap, no credits used.


GET  /templates?aspectRatio=16:9        → pick templates (one aspect ratio per video)
GET  /templates/:id                     → read editable layer keys
POST /projects  { scenes: [...] }       → one project, all scenes
POST /projects/:id/publish              → render all scenes + export ONE video
GET  /projects/:id/publish/status       → poll until phase === "completed"

Endpoints

1. List Templates

GET /templates

Query params: aspectRatio (16:9 | 16:12), category, search, limit (1–100), page.

cURL
curl "https://api-v2.wevi.ai/api/v2/templates?aspectRatio=16:9&limit=10" \
  -H "X-API-Key: wevi_live_YOUR_KEY"
Python (requests)
import requests

response = requests.get(
    "https://api-v2.wevi.ai/api/v2/templates",
    headers={"X-API-Key": "wevi_live_YOUR_KEY"},
    params={"aspectRatio": "16:9", "limit": 10},
)
templates = response.json()["data"]["templates"]

Each template includes aspectRatio, storyboard (bestUsedAs and fitsBeats across hook, problem, solution, proof, cta, transition), voiceover (seconds, maxChars, maxWords), requiresUiCapture and uiCaptureSlots. Templates with requiresUiCapture: true need a page capture before rendering; see the capture endpoints below.


2. Get Template Schema

GET /templates/:id

:id is the template ID or slug. data.layerMeta maps every layer key to its type, label, description, default value, validation and dropdown options.

cURL
curl "https://api-v2.wevi.ai/api/v2/templates/call-to-action" \
  -H "X-API-Key: wevi_live_YOUR_KEY"

3. Create Project

POST /projects

Put all scenes of the video in one request so they export as a single file. Every template must be published and share one aspect ratio.

cURL
curl -X POST "https://api-v2.wevi.ai/api/v2/projects" \
  -H "X-API-Key: wevi_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Automated Promo Video",
    "brand": { "websiteUrl": "focusflow.app" },
    "scenes": [
      {
        "templateId": "hero-showcase",
        "voiceoverText": "Meet FocusFlow, scheduling that runs itself.",
        "parameters": { "TXTTYPINGVAR": "Scale Your Production", "CLRTEXTTYPINGVAR": "#F8FAFC" }
      },
      {
        "templateId": "call-to-action",
        "parameters": { "TXTTYPINGVAR": "Start free today" }
      }
    ]
  }'

Response:

{
  "success": true,
  "data": {
    "id": "PROJECT_ID",
    "title": "Automated Promo Video",
    "status": "RENDER_PENDING",
    "aspectRatio": "16:9",
    "sandbox": false,
    "sceneCount": 2,
    "scenes": [{ "id": "…", "position": 1, "templateId": "…", "parameters": { "TXTTYPINGVAR": "Scale Your Production" } }],
    "nextStep": "Call POST /projects/:id/publish to render every scene and export one concatenated video."
  }
}

brand (optional) attaches or builds the brand used for logo, app-logo, avatar, background and color layers you leave unset: pass websiteUrl (Wevi fetches the logo, colors and fonts and reuses the brand on later requests), or logoUrl, name, primaryColor, secondaryColor. Alternatively pass brandId for an existing brand. If a template has a logo layer and the resolved brand has no logo, the request is refused with 400 and missingBrandAssets listing the scene and layer. Each returned scene includes autoFilled, the layer keys filled from the brand.

Unknown or locked parameter keys return 400 with the list of editable keys.

voiceoverText longer than the scene's budget (about 11 characters per usable second of template length, after a short beat at the cut) returns 400 naming the limit, because a line that runs past its scene pulls picture and voice apart. Each scene in the response carries voiceoverCharLimit.


3b. Update, add, remove, reorder and swap scenes

PATCH /projects/:projectId/scenes/:sceneId · POST /projects/:projectId/scenes · DELETE /projects/:projectId/scenes/:sceneId · PATCH /projects/:projectId/scenes/reorder · PATCH /projects/:projectId/scenes/:sceneId/template

Add takes the same body as a scene on create plus position; reorder takes { sceneIds } with every id once; swap takes { templateId, parameters? } and carries matching parameters over (dropped keys are returned). Text layers left unset are filled from the scene title, else the narration, else the project title; a required text layer with nothing to draw from returns 400 with missingText.

Body: { parameters?, voiceoverText?, title? }. Parameters merge over the scene's current values (null clears one); narration must fit the scene's budget. Use it to fix a failed scene, for example by setting a logo layer to an image URL, then publish again: only scenes whose inputs changed are re-rendered.


4. Publish (render + export)

POST /projects/:projectId/publish

Renders every scene, then queues one export that concatenates them and mixes music and voiceover.

Body (optional): quality (720p | 1080p | 4K, default 1080p), format (mp4 | webm), includeMusic, musicTrackId, includeVoiceover, voiceId, includeSoundEffects. GET /media/music?query= and GET /media/voices list the ids. Without musicTrackId Wevi picks a track matching the brand's tone. Scenes that fail before reaching the renderer (for example an unresolved layer) refund their render credit automatically; the status lists them with "Credit refunded".

Callback instead of polling: pass callbackUrl (public https) and optionally callbackSecret. Wevi drives the publish for you and POSTs one JSON event when it reaches completed, failed or render_failed:

{
  "event": "publish.completed",
  "requestId": "…", "projectId": "…", "phase": "completed",
  "videoUrl": "https://…mp4", "thumbnailUrl": "https://…jpg", "durationSeconds": 21.4,
  "export": { "id": "…", "status": "SUCCESS", "quality": "1080p", "format": "mp4", "errorMessage": null },
  "render": { "failures": [], "scenes": [ { "sceneId": "…", "state": "done", "previewUrl": "…" } ] },
  "attempt": 1, "sentAt": "2026-09-17T12:00:00.000Z"
}

Headers: x-wevi-event, x-wevi-request-id, x-wevi-timestamp, and when a secret was given x-wevi-signature: sha256=<HMAC-SHA256 of "timestamp.body">. Reply with any 2xx; anything else is retried after 1, 5 and 15 minutes. GET …/publish/status shows the delivery state under callback.

Music: a project created through the API has no track selected, so with includeMusic (default) Wevi assigns one from its library on the first publish and keeps it for later publishes; pass musicTrackId to choose a specific library track. The response includes music { trackId, title, artist, source }. Voiceover is generated from each scene's voiceoverText.

cURL
curl -X POST "https://api-v2.wevi.ai/api/v2/projects/PROJECT_ID/publish" \
  -H "X-API-Key: wevi_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "quality": "1080p" }'

5. Publish status

GET /projects/:projectId/publish/status

Poll every ~10 seconds until phase is completed. The render.scenes array carries each scene's state, previewUrl (the rendered scene alone) and error.

cURL
curl "https://api-v2.wevi.ai/api/v2/projects/PROJECT_ID/publish/status" \
  -H "X-API-Key: wevi_live_YOUR_KEY"

Response (when complete):

{
  "success": true,
  "data": {
    "phase": "completed",
    "message": "Video is ready. Use videoUrl to download it.",
    "sandbox": false,
    "render": { "state": "completed", "sceneCount": 2, "doneScenes": 2, "failedScenes": 0, "progressPercent": 100, "failures": [] },
    "export": { "id": "EXPORT_ID", "status": "SUCCESS", "progress": 100, "quality": "1080p", "durationSeconds": 12.4 },
    "videoUrl": "https://d2cl16wj5ibe2x.cloudfront.net/exports/…/EXPORT_ID.mp4",
    "thumbnailUrl": "https://…jpg"
  }
}

phase: idle · rendering · render_failed (see render.failures) · exporting · completed · failed (see export.errorMessage).


6. Low-level render & export endpoints

publish uses these internally. Call them directly only if you manage the render yourself.

MethodPathPurpose
POST/projects/:projectId/render/startStart scene renders
GET/projects/:projectId/render/statusScene render pipeline state
GET/exports/projects/:projectId/readinessWhether an export can be queued
POST/exports/projects/:projectIdQueue an export
GET/exports/:exportIdOne export record (outputUrl when SUCCESS)
GET/exports/:exportId/downloadShort-lived forced-download URL

6b. UI capture for capture templates

POST /projects/:projectId/scenes/:sceneId/auto-capture with { "url": "https://…", "focus": ["pricing table"], "selectors": ["#pricing"] } opens the page in a live browser, auto-selects components, frames the view on them, recaptures a clean background with them hidden, and makes the scene render-ready. Returns selected, candidates (ranked, with ids and number), captureId, screenshotUrl, frame, renderReady. 1 credit.

Optional fields: sessionId (capture the current view of a session opened with POST /browse/sessions; url then optional), steps (actions to run first, same shape as POST /browse/sessions/:id/actions), frame (auto default, current, full), targetFill (0.3–0.95), keepSession (returns sessionId), annotate (adds annotatedScreenshot, a small JPEG with numbered boxes), autoSelect: false (candidates and annotated screenshot only). The response includes timingsMs (openSession, steps, probe, frame, capture, finalize, plus the browser's own breakdown); the page is measured before the single capture and framed once when needed, over one browser connection.

Browsing endpoints, free of credits: POST /browse/sessions { url, projectId?, sceneId? } opens a session (viewport follows the scene's template); GET /browse/sessions/:id/outline returns headings, links, sections, scroll state and a small screenshot; POST /browse/sessions/:id/actions { actions: [{ type: "click", text: "Pricing" }, { type: "scroll", selector: "#plans" }] } runs steps (goto, click, type, hover, scroll, wait, press, back) and returns the outline; DELETE /browse/sessions/:id closes it. Agent sessions close after 10 minutes idle, when the project is published, or after an hour; each closed session records a BROWSE_SESSION usage event (first 5 minutes free, then 1 credit per started 5 minutes, sandbox free; a session swept for idleness is billed only up to its last use). Cookie and consent notices are closed automatically after each navigation and before each capture (reject or necessary-only when offered, else accept, else close). Guardrails: steps that look like payments, purchases, deletions or account changes are refused, at most 12 steps per call and 150 per session, only http(s) pages, and every step is logged on the project (metadata.agentBrowse, without typed text). GET /browse/logins reports whether a persistent browser profile exists for the account and DELETE /browse/logins resets it; sandbox keys always browse anonymously. Sessions opened with a live key return loginUrl, a page in the Wevi app where the user can log in themselves. Limits: 12 session opens per minute, 60 action or outline calls per minute, 3 open agent sessions per account, and a daily browser-time allowance of 120 minutes (live) or 40 minutes (sandbox), each answered with 429 and a reset time when exceeded. Sessions are locked to the site they were opened for plus any site reached through an explicit goto; a step that lands elsewhere is undone and reported as refused. Browser logins are stored per user only and are never shared with other accounts.

Behind a login: every outline (and the result of actions) includes authWall { detected, signals }, routes (the app's own views from its navigation, same site only) and loginUrl (agent sessions). When the page is a sign-in screen, send the user loginUrl; they sign in there and press "I am signed in", which calls POST /browse/sessions/:id/human-done. Poll GET /browse/sessions/:id/status (humanSignedInAt, humanHoldUntil, expiresAt) or re-read the outline until authWall.detected is false, then navigate to a route and capture. Auto-capture on a sign-in screen returns loginRequired: true with authWall and loginUrl, keeps the session open and charges nothing. Redirects to the same site's other subdomains and to hosted identity providers are allowed during steps.

POST /projects/:projectId/scenes/:sceneId/capture-selection with { "captureId", "selectedComponentIds": [] } re-picks from that capture. Free.

POST /projects/:projectId/scenes/:sceneId/capture-image with { "imageUrl" } uses a still image as the captured page for whole-page background templates. Free.

GET /projects/:projectId/captures/pending lists scenes that still need a capture. POST /projects/:id/publish refuses with that list until they are done.

7. Credits

GET /billing/credits

Balance for the calling account: plan allowance, purchased credits, per-action costs, packs and the top-up URL.

{
  "success": true,
  "data": {
    "plan": { "slug": "pro-monthly", "name": "Pro", "allowance": 150, "used": 38, "remaining": 112, "resetMode": "monthly", "periodEnd": "2026-10-01T00:00:00.000Z" },
    "purchased": { "balance": 200 },
    "totalAvailable": 312,
    "costs": { "SCENE_RENDER": 1, "EXPORT_720P": 1, "EXPORT_1080P": 2, "EXPORT_4K": 4, "STORYBOARD_DRAFT": 1, "UI_CAPTURE": 1 },
    "packs": [{ "id": "starter", "name": "Starter", "credits": 50, "priceCents": 1500 }],
    "topUpUrl": "https://app.wevi.ai/app/profile?id=api-keys&topup=1"
  }
}

packs is the live catalog (ids, credits and prices can change); pass one of its ids to checkout. GET /billing/credits/ledger?limit=50 — recent credit movements. POST /billing/credits/checkout { "packId": "builder" } — starts a Stripe Checkout for a pack (web sessions).

8. AI storyboard draft

POST /ai/storyboard/draft — body: prompt (required), brandName, brandUrl, brandVoice, brandId, maxScenes (3–12), targetDurationSeconds (10–180), aspectRatio (16:9 default or 16:12), captureUi (true prefers templates that show real UI, false avoids them). Costs 1 credit on a live key; takes 20–60 seconds.

The draft is planned against the published catalog and already satisfies the create rules: one aspect ratio, published templates only, narration inside each scene's voiceover budget (complete sentences, rewritten rather than cut), on-screen copy per text layer, capture scenes flagged. The response carries draft (title, narrativeAngle, aspectRatio, totalDurationSeconds, scenes[] with order, templateId, templateName, storyboardRole, durationSeconds, voiceover, voiceoverBudget { seconds, maxChars, maxWords }, onScreenText, parameters, textSource, textLayerKeys, requiresUiCapture, captureSlots, captureHint), createPayload (a valid body for POST /projects, including brand.websiteUrl when a website was given), notes and nextStep. Sandbox keys get at most 4 scenes.

9. Web UI capture

POST /browse/capture — body: url (required), selector, viewport (desktop | tablet | mobile). Costs 1 credit on a live key.


Narration

Narration is voiced as one continuous take for the whole video and then placed on the scenes, so the read flows across cuts. This happens automatically at export. GET /projects/:id/narration reports the state (ready, per-scene segments with timeline positions); POST /projects/:id/narration builds or refreshes the take after script or voice changes. Narration lines still have to fit each scene's voiceover budget.

Plan features that gate agent capabilities

Four plan features control what agents can do, and admins can switch them per plan (or per enterprise account): mcp_capture (UI captures), mcp_browse (live browser sessions), storyboard_draft (AI storyboard drafts) and export_4k (4K exports). A call that needs a feature the plan does not include returns 403 with the feature name in the message. Sandbox keys are not gated by these features (they are already capped at 720p with a watermark and no credit use).

One-time purchases

GET /billing/one-time returns the account's watermark-free passes, render top-ups and what can be bought. POST /billing/one-time/export-pass/checkout { returnPath? } and POST /billing/one-time/top-up/checkout { packId, returnPath? } start a Stripe checkout; POST /billing/one-time/confirm { sessionId } confirms a purchase on return (idempotent with the webhook). POST /billing/one-time/export-pass/redeem { projectId } spends one pass on a project: its scenes render again without the watermark and it can export at 1080p. Render top-ups raise the plan's limit for a feature such as scene render revisions and show up in GET /billing/usage as topUp on that feature.

Key spending caps

A key can carry a monthly spending limit on top of your credit balance: POST /api-keys accepts monthlyCreditCap, and PATCH /api-keys/:id sets or removes it ({ "monthlyCreditCap": 100 } or null). The cap counts credits spent through that key in the current calendar month (UTC) and resets on the 1st. Past the cap, metered calls return 402 with code: "KEY_MONTHLY_CAP", the key's usage and the reset date; other keys and the balance are unaffected. GET /billing/credits includes a key block with the cap and month-to-date usage when called with an API key, and the usage report lists monthlyCreditCap and monthCredits per key. Sandbox keys spend no credits, so caps do not apply to them.

Status Codes & Error Handling

CodeMeaningResolution
200 / 201SuccessRequest succeeded.
400Bad RequestUnknown layer keys, mixed aspect ratios, unpublished template, or project not ready. The message says what to fix.
401UnauthorizedMissing, invalid, expired, or revoked API Key.
402Payment RequiredNot enough API credits. Body carries creditsRequired, creditsAvailable and topUpUrl. Buy a pack or upgrade.
403ForbiddenQuality not available on your plan.
404Not FoundTemplate, project, brand or export does not exist (or the template is not published).
429Too Many Requests120 requests/minute per key; publish and render start are limited to 10/minute.