# FacelessGenie REST API (Beta)

> Create complete faceless videos — script, voiceover, visuals, captions, and a final rendered mp4 — from one REST call. Videos render in 5–20 minutes and consume account credits; failed renders refund automatically.

- Base URL: `https://www.facelessgenie.ai/api/v1`
- Auth: `Authorization: Bearer fg_YOUR_API_KEY` on every request — create keys at https://www.facelessgenie.ai/settings?tab=api (shown once, stored hashed, revocable).
- Errors always use `{ "error": { "code", "message" } }`.
- Status: **Beta.** The API and MCP server are new — response shapes may gain fields and rough edges may change while we harden them. Questions or problems: hello@facelessgenie.ai.
- Human docs: https://www.facelessgenie.ai/developers · Claude connector: https://www.facelessgenie.ai/claude · This file: https://www.facelessgenie.ai/developers.md

## Use the defaults

Every format ships with tuned defaults — the same models, voice, and settings the FacelessGenie app uses. They are the recommendation. Omit `models`, `voiceId`, `voiceProvider`, and caption styling unless you deliberately want something specific; premium overrides cost more credits without necessarily looking better.

## Quickstart

1. Create an API key in Settings → API.
2. `POST https://www.facelessgenie.ai/api/v1/videos` with `{ "topic": "...", "formatId": "ai-images-short" }` → returns `202` with an `id` and `creditsCost`.
3. Poll `GET https://www.facelessgenie.ai/api/v1/videos/{id}` every 30–60 seconds until `status` is `COMPLETED` — `videoUrl` is the finished mp4.

## MCP server (Beta)

AI agents can drive everything below through the hosted MCP server at `https://www.facelessgenie.ai/api/mcp`.

- Claude: Settings → Connectors → Add custom connector → paste `https://www.facelessgenie.ai/api/mcp` and sign in (OAuth, no key to paste). Guide: https://www.facelessgenie.ai/claude
- Other MCP clients (Claude Code, Cursor, ChatGPT, …): pass `Authorization: Bearer fg_YOUR_API_KEY` as a header. Per-tool guides: https://www.facelessgenie.ai/connect
- Tools: `list_formats`, `list_models`, `create_video`, `preview_credits`, `get_video_status`, `list_videos`, `delete_video`, `create_asset_upload`, `register_asset`, `import_asset_from_url`, `upload_asset`, `list_assets`, `get_account`, `get_credit_balance`, `buy_credits`.

## Endpoints

### Catalog

Everything is self-describing — start here to see what you can make and what it costs.

#### GET /formats — List formats

Every video format you can create. Each entry carries its aspect ratio, duration options (in durationUnit), the extra inputs it accepts (pass values via formatInputs), and — for character formats — castSlots plus the characters/voices to fill them with.

Request:

```bash
curl https://www.facelessgenie.ai/api/v1/formats \
  -H "Authorization: Bearer fg_YOUR_API_KEY"
```

Response:

```json
{
  "formats": [
    {
      "id": "ai-images-short",
      "name": "AI Images Short",
      "description": "Vertical short with AI still imagery",
      "category": "narrative",
      "aspectRatio": "9:16",
      "durationUnit": "sec",
      "durationOptions": [30, 60, 90],
      "defaultDuration": 60,
      "defaultVoiceMode": "narrator",
      "requiresCharacterReference": false,
      "modelOptions": {
        "image": [
          { "tier": "budget", "id": "p-image", "name": "P-Image", "credits": 1, "premium": false },
          { "tier": "high", "id": "nano-banana-pro", "name": "Nano Banana Pro", "credits": 8, "premium": true }
        ],
        "video": []
      },
      "inputs": [
        {
          "id": "genre",
          "label": "Genre",
          "kind": "multi-select",
          "required": false,
          "options": [{ "id": "horror", "label": "Horror" }, "…"]
        }
      ]
    },
    {
      "id": "char-duo-explainer-vlog",
      "name": "Character Duo Explainer",
      "aspectRatio": "9:16",
      "castSlots": [
        { "id": "host", "label": "Host", "required": true, "…": "…" }
      ],
      "castOptions": {
        "characters": [{ "id": "documentary-narrator", "label": "Documentary Narrator", "tier": "free" }],
        "voices": [{ "id": "voice-narrator-standard", "label": "Standard Narrator (Male)", "tier": "free" }]
      }
    }
  ]
}
```

Note: Trimmed for readability — the live response includes every format and full input definitions. modelOptions lists the ONLY model ids create-video accepts for that format (same choices as the app); formats without modelOptions run a tuned pipeline with no overrides.

#### GET /models — List selectable models

The models a video can actually use — one image model and (for clip formats) one video model per quality tier, exactly matching the choices in the app. Pass ?formatId= for a format's exact allowed ids; these are the ONLY values create-video accepts in models.image / models.video. Script, TTS, and music models aren't selectable — every format uses its tuned defaults.

Parameters:

- `formatId` (string) — Format to get options for (from GET /formats). Omit for the global default tiers.

Request:

```bash
curl "https://www.facelessgenie.ai/api/v1/models?formatId=ai-images-short" \
  -H "Authorization: Bearer fg_YOUR_API_KEY"
```

Response:

```json
{
  "formatId": "ai-images-short",
  "note": "This format only generates images (no video clips). Pass one of these ids in models.image, or omit for the default.",
  "modelOptions": {
    "image": [
      { "tier": "budget", "id": "p-image", "name": "P-Image", "credits": 1, "premium": false },
      { "tier": "standard", "id": "grok-imagine-image-quality", "name": "Grok Imagine Quality", "credits": 2, "premium": false },
      { "tier": "pro", "id": "gpt-image-2", "name": "OpenAI GPT Image 2", "credits": 5, "premium": true },
      { "tier": "high", "id": "nano-banana-pro", "name": "Nano Banana Pro", "credits": 8, "premium": true }
    ],
    "video": []
  }
}
```

#### GET /voices — List voices

Narrator voices grouped by provider. Use a voice's id + its provider in create-video's voiceId / voiceProvider fields.

Request:

```bash
curl https://www.facelessgenie.ai/api/v1/voices \
  -H "Authorization: Bearer fg_YOUR_API_KEY"
```

Response:

```json
{
  "voices": {
    "kokoro":     [{ "id": "af_bella", "name": "Bella", "…": "…" }],
    "elevenlabs": ["… 21 voices …"],
    "fish":       ["… 35 voices …"]
  }
}
```

#### GET /account — Get account

The account your key belongs to, with the live credit balance. monthly resets with your plan; additional never expires.

Request:

```bash
curl https://www.facelessgenie.ai/api/v1/account \
  -H "Authorization: Bearer fg_YOUR_API_KEY"
```

Response:

```json
{
  "email": "you@example.com",
  "name": "Your Name",
  "plan": "Creator",
  "credits": { "monthly": 1747, "additional": 200, "total": 1947 }
}
```

### Videos

One POST runs the whole pipeline — script, voiceover, visuals, captions, final render.

#### POST /videos — Create a video

Creates a complete video from a topic and queues the pipeline. Returns 202 immediately — poll GET /videos/{id} until COMPLETED. Credits are charged up front; failures refund automatically.

Parameters:

- `topic` (string, required) — What the video is about (3–4000 chars).
- `formatId` (string, required) — Any id from GET /formats.
- `duration` (number) — In the format's durationUnit. Defaults to the format's defaultDuration.
- `niche` (string) — Content niche, e.g. "education". Default "general".
- `voiceId / voiceProvider` (string) — Optional — each format picks a tuned narrator by default. Only set to use a specific voice from GET /voices (provider: kokoro | elevenlabs | fish).
- `models` (object) — Advanced — skip this. Defaults are tuned per format and recommended. Overrides: { image?, video? } — each must be one of the format's modelOptions ids (GET /formats or GET /models?formatId=); anything else is rejected. Script/TTS/music models aren't selectable.
- `resolution` ("720p" | "1080p") — Output resolution.
- `musicMood` (string) — calm | uplifting | dramatic | mysterious | energetic | epic | lofi | cinematic | suspense | dreamy.
- `captions` (object) — { enabled, position, style, mode, fontSize, color, font, case }.
- `formatInputs` (object) — The format's extra inputs from GET /formats (e.g. genre, articleUrl).
- `visualStyle` (string) — cinematic | realistic | ghibli | anime | pixar | …
- `characterReferenceUrl` (url) — Reference image for character-consistent formats (upload via assets first).
- `customMusicUrl` (url) — Your own soundtrack — must be an assetUrl from the assets endpoints.
- `watermarkText` (string) — Custom watermark (paid plans).
- `cast` (object) — For formats with castSlots: { [slotId]: { characterId, voiceId, appearance } }.

Request:

```bash
curl -X POST https://www.facelessgenie.ai/api/v1/videos \
  -H "Authorization: Bearer fg_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "topic": "5 facts about the deep ocean that sound fake",
    "formatId": "ai-images-short",
    "duration": 60,
    "resolution": "1080p",
    "musicMood": "mysterious"
  }'
```

Response:

```json
HTTP/1.1 202 Accepted

{
  "id": "fab5fce2-a770-4052-badf-b489c7153124",
  "status": "queued",
  "creditsCost": 486
}
```

#### GET /videos/{id} — Get a video / poll status

Status flows PENDING → PROCESSING → RENDERING → COMPLETED (or FAILED, which auto-refunds). Poll every 30–60 seconds; renders take 5–20 minutes. When COMPLETED, videoUrl is your finished mp4.

Request:

```bash
curl https://www.facelessgenie.ai/api/v1/videos/fab5fce2-a770-4052-badf-b489c7153124 \
  -H "Authorization: Bearer fg_YOUR_API_KEY"
```

Response:

```json
{
  "id": "fab5fce2-a770-4052-badf-b489c7153124",
  "title": "5 facts about the deep ocean that sound fake",
  "status": "PROCESSING",
  "formatId": "ai-images-short",
  "videoType": "SHORT",
  "resolution": "1080p",
  "creditsCost": 486,
  "videoUrl": null,
  "thumbnailUrl": null,
  "isPublic": false,
  "progress": { "step": "images", "percent": 62, "message": "Generating scene 8/12" },
  "errorReason": null,
  "createdAt": "2026-07-09T18:37:46.999Z",
  "updatedAt": "2026-07-09T18:41:02.113Z"
}
```

Note: When status is COMPLETED, videoUrl and thumbnailUrl hold permanent CDN links.

#### GET /videos — List videos

Your videos, newest first.

Parameters:

- `limit` (number) — 1–200, default 50.
- `videoType` (string) — SHORT or LONGFORM.

Request:

```bash
curl "https://www.facelessgenie.ai/api/v1/videos?limit=20&videoType=SHORT" \
  -H "Authorization: Bearer fg_YOUR_API_KEY"
```

Response:

```json
{
  "videos": [
    {
      "id": "aa450193-…",
      "title": "The Beast Beneath the Moss",
      "status": "COMPLETED",
      "formatId": "character-video",
      "videoType": "SHORT",
      "videoUrl": "https://assets.facelessgenie.ai/renders/…/video.mp4",
      "thumbnailUrl": "https://assets.facelessgenie.ai/renders/…/thumb.jpg",
      "isPublic": false,
      "progress": null,
      "errorReason": null,
      "createdAt": "2026-07-01T09:12:00.000Z",
      "updatedAt": "2026-07-01T09:26:41.000Z"
    }
  ]
}
```

#### DELETE /videos/{id} — Delete a video

Permanently deletes a video and its assets. If the pipeline is still running it's cancelled and unspent credits are refunded. Cannot be undone.

Request:

```bash
curl -X DELETE https://www.facelessgenie.ai/api/v1/videos/fab5fce2-… \
  -H "Authorization: Bearer fg_YOUR_API_KEY"
```

Response:

```json
{ "ok": true, "refundedCredits": 486 }
```

#### POST /videos/preview-credits — Preview credit cost

Exactly what a create call will cost — same body as POST /videos (topic optional), no side effects, nothing charged.

Request:

```bash
curl -X POST https://www.facelessgenie.ai/api/v1/videos/preview-credits \
  -H "Authorization: Bearer fg_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "formatId": "ai-images-short", "duration": 60, "resolution": "1080p" }'
```

Response:

```json
{
  "formatId": "ai-images-short",
  "credits": 486,
  "estimate": false,
  "models": {
    "script": "gemini-3.5-flash",
    "tts": "kokoro",
    "image": "grok-imagine-image-quality",
    "i2v": "wan-2-5-fast",
    "music": "minimax-music-2-6"
  },
  "durationSec": 60
}
```

Note: Character formats return { "estimate": true } — their final cost depends on the generated script.

### Assets

Bring your own character references, music, or clips.

#### POST /assets/presign — Get an upload URL

Two-step upload for files up to 500 MB: presign, then PUT your bytes straight to storage with the same Content-Type. Use the returned assetUrl in create-video or register it below.

Parameters:

- `fileName` (string, required) — Original file name (extension is kept).
- `mimeType` (string, required) — image/*, video/* or audio/*.
- `sizeBytes` (number) — File size — validated against per-type limits.

Request:

```bash
curl -X POST https://www.facelessgenie.ai/api/v1/assets/presign \
  -H "Authorization: Bearer fg_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fileName": "my-character.png", "mimeType": "image/png", "sizeBytes": 204800 }'

# then upload the bytes (within 10 minutes):
curl -X PUT "UPLOAD_URL_FROM_RESPONSE" \
  -H "Content-Type: image/png" \
  --data-binary @my-character.png
```

Response:

```json
{
  "uploadUrl": "https://…r2.cloudflarestorage.com/…&X-Amz-Signature=…",
  "proxyUploadUrl": "https://www.facelessgenie.ai/api/v1/assets/upload/eyJ1c2VySWQiOi…",
  "assetUrl": "https://assets.facelessgenie.ai/facelessgenie-uploads/…/xK9dPq.png",
  "expiresInSec": 600
}
```

Note: Sandboxed environments with egress allowlists (e.g. AI agent sandboxes) often can't reach the R2 uploadUrl host — PUT the same bytes to proxyUploadUrl instead; it's on this API's own domain and needs no extra headers. If both are blocked, the user can upload in the browser at /library?tab=uploads (assets show up in GET /assets), or allowlist www.facelessgenie.ai for the agent's network egress.

#### POST /assets/import — Import from a URL

Already hosted somewhere? We fetch the file server-side (direct https link, image / video / audio, up to 100 MB) and save it to your media library in one call.

Parameters:

- `url` (url, required) — Direct https link to the media file.
- `name` (string) — Display name — defaults to the file name.
- `kind` (string) — character | image | video | audio — defaults from the file type.

Request:

```bash
curl -X POST https://www.facelessgenie.ai/api/v1/assets/import \
  -H "Authorization: Bearer fg_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/track.mp3", "name": "My track" }'
```

Response:

```json
{
  "id": "7bd27d58-9ace-456f-ad29-412d5b4c6bcd",
  "kind": "audio",
  "name": "My track",
  "assetUrl": "https://assets.facelessgenie.ai/facelessgenie-uploads/…/xK9dPq.mp3",
  "mimeType": "audio/mpeg",
  "sizeBytes": 204800
}
```

#### POST /assets — Register an asset

Saves an uploaded file into your media library so it shows up in the dashboard and GET /assets.

Parameters:

- `kind` (string, required) — character | image | video | audio.
- `name` (string, required) — Display name.
- `url` (url, required) — The assetUrl returned by presign.
- `mimeType / sizeBytes / durationSec` (mixed) — Optional metadata.

Request:

```bash
curl -X POST https://www.facelessgenie.ai/api/v1/assets \
  -H "Authorization: Bearer fg_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "character", "name": "My character", "url": "ASSET_URL", "mimeType": "image/png" }'
```

Response:

```json
{
  "id": "7bd27d58-9ace-456f-ad29-412d5b4c6bcd",
  "kind": "character",
  "name": "My character",
  "url": "https://assets.facelessgenie.ai/facelessgenie-uploads/…/xK9dPq.png",
  "createdAt": "2026-07-09T18:41:30.019Z"
}
```

#### GET /assets — List assets

Your media library — reuse any url in create-video.

Parameters:

- `kind` (string) — character | image | video | audio.
- `limit` (number) — 1–200, default 60.

Request:

```bash
curl "https://www.facelessgenie.ai/api/v1/assets?kind=character&limit=20" \
  -H "Authorization: Bearer fg_YOUR_API_KEY"
```

Response:

```json
{
  "assets": [
    {
      "id": "7bd27d58-…",
      "kind": "character",
      "name": "My character",
      "url": "https://assets.facelessgenie.ai/…/xK9dPq.png",
      "mimeType": "image/png",
      "sizeBytes": 204800,
      "durationSec": null,
      "createdAt": "2026-07-09T18:41:30.019Z"
    }
  ]
}
```

## Rate limits

- 120 requests / minute
- 10 video creations / minute
- 2 videos generating at once
- 30 uploads / minute

429 responses include a `Retry-After` header — respect it.

## Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `validation_error` | The body or params are invalid — the message names the field. |
| 401 | `unauthorized` | Missing, invalid, or revoked API key. |
| 402 | `insufficient_credits` | Not enough credits for this video. Top up and retry. |
| 403 | `plan_required` | No active subscription, or the plan doesn't allow this configuration. |
| 404 | `not_found` | The resource doesn't exist or isn't yours. |
| 429 | `rate_limited` | Slow down — respect the Retry-After header. |
| 5xx | `internal_error` | Our side. Failed creations refund automatically. |

## Credits

Same pricing as the app — format, duration, and any model overrides set the cost. `POST https://www.facelessgenie.ai/api/v1/videos/preview-credits` shows the exact cost before creating; `GET https://www.facelessgenie.ai/api/v1/account` shows the balance. Failed renders refund automatically; deleting an in-flight video refunds what wasn't spent.
