# REST API reference # REST API reference Base URL: `https://frugal-anteater-681.convex.site` · All bodies are JSON · Authenticate every `/v1` request with ``` Authorization: Bearer ss_live_... ``` ## Conventions - **Jobs are asynchronous.** Endpoints that do work (`/v1/styles`, `/v1/edits`, `/v1/recordings`, `/v1/fetch`) return `202` with a [job](#jobs) immediately. Poll `GET /v1/jobs/{id}` every few seconds. - **Credits** are charged when a job is accepted and refunded automatically if it fails. See [Credits](billing.html). - **Getting a key:** [request access](/#access). Keys work only after a person approves the account; an unapproved key gets `403 key_pending_approval`. - **Errors** have one shape: ```json { "error": { "code": "insufficient_credits", "message": "this costs 5 credit(s); balance is 2. …" } } ``` | Status | Codes | |---|---| | 400 | `missing_field`, `invalid_field`, `invalid_json`, `invalid_id`, `invalid_url`, `invalid_pack`, `unknown_account`, `platform_mismatch` | | 401 | `unauthorized` — missing, invalid or revoked key | | 402 | `insufficient_credits` | | 403 | `key_pending_approval`, `forbidden` (needs an admin key), `fetch_disabled` | | 404 | `not_found` | | 409 | `conflict`, `not_pending` | | 429 | `rate_limited`, `too_many_jobs` | | 502 / 503 | `zernio_error`, `not_configured` | ## Account ### `GET /v1/me` ```json { "orgId": "js7…", "keyScope": "agent", "name": "Acme", "credits": 93, "autoPublish": false, "allowHostedFetch": false, "maxConcurrentJobs": 4, "apiVersion": "2026-10-08" } `apiVersion` changes whenever the endpoint or MCP tool surface changes; MCP clients that cache the tool list should reconnect when it does. ``` ## Assets An asset is any file in your account: an uploaded clip or song, a reference, a finished edit, a recording. Uploading takes three calls, so large files go straight to storage instead of through the API. ### `POST /v1/assets/upload-url` Returns a one-time URL: `{ "uploadUrl": "https://…" }`. Send the raw bytes to it: ```bash curl -X POST "$uploadUrl" -H "Content-Type: video/mp4" --data-binary @clip.mp4 # → { "storageId": "kg2…" } ``` ### `POST /v1/assets` Register the upload: `{ "storageId": "kg2…", "filename": "clip.mp4" }` → `201` with the asset: ```json { "id": "j97…", "kind": "video", "role": "upload", "filename": "clip.mp4", "contentType": "video/mp4", "size": 991017, "url": "https://…", "source": null, "jobId": null, "createdAt": 1791328110667 } ``` `kind` is `video`, `audio`, `image`, `json` or `other`, from the uploaded Content-Type. ### `GET /v1/assets` · `GET /v1/assets/{id}` · `DELETE /v1/assets/{id}` List the 100 most recent, read one (with a download `url`), or delete one and its file permanently. ## Styles ### `POST /v1/styles` — analyze a reference (2 credits) ```json { "assetId": "j97…", "semantic": true } ``` `semantic` (default `true`) adds vision-model shot roles, descriptions and caption style on top of the measured analysis. When the job succeeds, `result.styleId` is the new style and `result` summarizes it: ```json { "styleId": "jn7…", "aspect": "9:16", "duration": 17.8, "shot_count": 7, "bpm": 99.4, "cuts_on_beat_ratio": 0.71, "semantic": true, "summary": "Fast result-first hook, …", "pacing": { "shot_count": 7, "mean_shot": 2.54, "median_shot": 0.73, "cuts_per_minute": 20.3, … } } ``` ### `GET /v1/styles` · `GET /v1/styles/{id}` `GET /v1/styles/{id}` includes the full `spec` — see [Styles and edits](styles.html#the-style-spec). ## Edits ### `POST /v1/edits` — cut your clips to a style (5 credits) | Field | Type | | |---|---|---| | `styleId` | string | **Required.** | | `clipAssetIds` | string[] | **Required.** 1–30 of your video assets. | | `musicAssetId` | string | An audio asset. Cuts snap to its beat when the style is beat-driven. | | `musicStart` | number | Seconds into the track to start (default 0). | | `captions` | string[] | Caption lines in order (up to 40, 200 chars each). The first is the hook. | | `brief` | string | What the video is about. Used to write captions when `captions` is omitted. | | `aspect` | string | `9:16`, `4:5`, `1:1`, `4:3` or `16:9`. Defaults to the reference's. | | `duration` | number | Target length, 3–180 s. Defaults to the reference's. | | `keepClipAudio` | boolean | Mix your clips' own sound. Default: on without music, off (ducked) with music. | | `semantic` | boolean | Use the vision model to pick the best moments (default `true`). | The finished job's `outputs` contain the `edit` (mp4) and the `plan` (JSON: which clip and in-point filled each shot). `result.qa` is the automatic quality report; `result.notes` explains decisions such as beat snapping and layout. ## Generation See [Generating images, characters and video](generation.html) for every option. | Endpoint | | |---|---| | `POST /v1/characters` | `{ name, description, referenceAssetIds?, aspect?, resolution? }` → a stored character whose sheet is generated; poll until `ready` | | `GET /v1/characters` · `GET /v1/characters/{id}` · `DELETE /v1/characters/{id}` | List, read (with `sheetUrl`), delete | | `POST /v1/images` | `{ kind, brief, characterIds?, references?, aspect?, resolution?, n?, estimate? }` → job; outputs are images | | `POST /v1/videos` | `{ brief, characterIds?, duration?, resolution?, aspect?, imageAssetId?, referenceAssetIds?, referenceVideoAssetIds?, referenceAudioAssetIds?, estimate? }` → job, or a price with `estimate: true` | | `POST /v1/voice` | `{ text, voice?, direction?, stability?, perform? }` → job; output is audio | | `POST /v1/lipsync` | `{ videoAssetId, audioAssetId, pro? }` → job | Generation results include `notes` on choices made. The production craft behind them stays server-side. ## Models, skills, swaps, edits | | | |---|---| | `GET /v1/models` | The image, video and swap models you can pick, with what each supports. | | `POST /v1/skills` · `GET /v1/skills` · `GET/POST/DELETE /v1/skills/{id}` | Your own versioned image recipes; `skillIds` on images, characters, edits and brand kits. | | `POST /v1/images/edit` | Masked edit of a real photo: only the mask is painted, the rest is untouched. | | `POST /v1/swaps` | `object_swap` / `motion_transfer`: keep the footage, change the subject. | | `POST /v1/videos/{id}/finalize` · `draft: true` | Cheap 480p drafts, then the keeper at 720p/1080p. | | `POST /v1/jobs/{id}/cancel` | Cancel with a full refund. | | `POST /v1/upscales` | Lanczos resample to 2160p. | | `POST /v1/dialogue` · `GET /v1/voices` · `POST /v1/voices/clone` | Multi-speaker tracks with per-line timings; your own ElevenLabs voices and clones when your key is connected. | | `GET /v1/providers` · `POST/DELETE /v1/providers/{higgsfield\|elevenlabs}` | Connect your own provider keys (admin, where enabled). | | `POST /v1/brands/analyze` | A suggested brand kit from your boards. | See [Generation](generation.html). ## Explainer videos Brand kits, scripts and renders have their own page: [Explainer videos](explainers.html). | | | |---|---| | `POST /v1/brands` · `GET /v1/brands` · `GET /v1/brands/{id}` · `POST /v1/brands/{id}` · `DELETE /v1/brands/{id}` | Brand kits: name, description, tone, colors, font, logo, product images with descriptions, music. Free. | | `POST /v1/scripts` | `{ brandId, brief, duration?, aspect?, language? }` → job; `result.script` is an editable scene list. 2 credits. | | `POST /v1/explainers` | `{ brandId, brief | script, aspect?, captions?, voiceover?, musicAssetId? }` → job; the video plus the rendered script. 12 credits. | ## Recordings ### `POST /v1/recordings` — record a public web app (3 credits) ```json { "url": "https://example.com", "viewport": "mobile", "steps": [ { "type": "hold", "seconds": 2 }, { "type": "click", "selector": "text=Pricing" }, { "type": "scroll", "pixels": 900 }, { "type": "hold", "seconds": 2 } ] } ``` See [Recording](recording.html) for every step type. Output: a `recording` asset (mp4) and `result.marks`, the timestamp of every step for cutting. ## Fetching ### `POST /v1/fetch` — a reference from a social URL (1 credit) `{ "url": "https://www.tiktok.com/@…/video/…", "start": 3, "end": 18 }`. `start`/`end` (seconds, optional) keep only that section — handy for a 4–30 s reference clip for `POST /v1/videos`. Hosted fetching is a per-account setting: if it is off you get `403 fetch_disabled` — ask us to enable it (`POST /v1/reports` works) and confirm you have the rights to the clips, or fetch on your own machine with the local MCP server and upload instead. Output: a `fetched` asset with `source` attribution (and the `trim` applied). ## Jobs ### `GET /v1/jobs/{id}` · `GET /v1/jobs` ```json { "id": "jd7…", "kind": "replicate", "status": "succeeded", "progress": "done", "error": null, "result": { … }, "creditsCharged": 5, "refunded": false, "attempts": 1, "outputs": [ { "assetId": "j95…", "role": "edit", "kind": "video", "filename": "edit.mp4", "url": "https://…" } ], "createdAt": 1791469255706, "startedAt": 1791469256012, "finishedAt": 1791469281420 } ``` `status`: `queued` → `running` → `succeeded` | `failed`. A job with no word from the worker for three hours is marked failed and refunded. ## Social accounts and posts See [Posting](social.html) for the approval model. | Endpoint | | |---|---| | `GET /v1/social/accounts` | Connected accounts: `{ id, platform, username, displayName, status }` | | `POST /v1/social/connect` | `{ "platform": "tiktok", "redirectUrl": "https://…" }` → `{ "authUrl": "…" }` | | `POST /v1/posts` | Draft a post (1 credit). See below. | | `GET /v1/posts` | Posts and their status | | `POST /v1/posts/{id}/approve` | **Admin key.** Publish or schedule a pending post. | | `POST /v1/posts/{id}/reject` | **Admin key.** Reject it; its credit is refunded. | `POST /v1/posts`: ```json { "assetId": "j95…", "caption": "Dinner in one search #recipes", "targets": [ { "platform": "tiktok", "accountId": "6a62…" } ], "scheduledFor": "2026-10-12T18:00:00-04:00" } ``` Give `scheduledFor` (ISO 8601 with an offset, or with `timezone`) or `"publishNow": true`. Post statuses: `pending_approval` → `approved` → `scheduled` | `published` | `failed`, or `rejected`. ## Team Members, roles, keys, per-person credit caps and the shared library have their own page: [Team](team.html). | | | |---|---| | `GET /v1/members` · `POST /v1/members` · `GET/POST/DELETE /v1/members/{id}` · `POST /v1/members/{id}/keys` | People on the account, each with their own key and role. | | `GET /v1/keys` · `DELETE /v1/keys/{id}` | Every live key and whose it is. | | `GET /v1/library` | Brand kits, characters, styles, finished videos and uploads the team can reuse, with `createdBy`. | ## Reporting problems Found a bug, want a feature, or have a question? File it through the API — agents can do this on their own when something goes wrong. Reports are free. ### `POST /v1/reports` | Field | Type | | |---|---|---| | `title` | string | **Required.** Up to 200 characters. | | `description` | string | **Required.** What happened, what you expected, steps to reproduce. Up to 10,000 characters. | | `kind` | string | `bug` (default), `feature` or `question`. | | `severity` | string | `low`, `normal` (default), `high` or `blocking`. | | `jobId` | string | The job involved. Its status, error, inputs and QA report are attached automatically. | ```json { "kind": "bug", "severity": "high", "jobId": "jd7…", "title": "Captions cut off on 4:5 edits", "description": "The second caption line is clipped at the top. Expected it inside the frame." } ``` Returns `201` with the report. `GET /v1/reports` and `GET /v1/reports/{id}` show each report's `status` — `open`, `acknowledged`, `fixed`, `wont_fix` or `closed` — and the team's `response`. ## Credits | Endpoint | | |---|---| | `GET /v1/billing` | `{ credits, costs, packs }` | | `GET /v1/billing/ledger` | Every charge, refund and purchase, newest first |