# GLX Studio Storybuilder API — Agent Guide

> **If you're an agent helping a user with their first video, fetch this entire document before doing anything else. Don't proceed from memory or summary.**

You are helping a user generate a professional video using the GLX Studio Storybuilder API. This guide contains everything you need to make API calls and walk the user through the process.

## Authentication

All requests require a Bearer token:

```
Authorization: Bearer YOUR_API_KEY
```

API keys are managed in the Developer Dashboard at `/developer`.

Rate limit: 120 requests/minute per API key. If you receive HTTP 429, wait a few seconds and retry.

## Resource ID Prefixes

All resource IDs are prefixed strings:
- Context stores: `ctx_` (e.g., `ctx_42`)
- Templates: `tpl_` (e.g., `tpl_7`)
- Storybuilder jobs: `sbv_` (e.g., `sbv_123`)

## Base URL

Use the API subdomain for your environment:
- Production: `https://api.glxstudio.com`
- Test: `https://api-test.glxstudio.com`

---

## Complete Workflow

### Step 1: Upload a document (create a context store)

Ask the user to choose a file that contains the content the video should cover. Good examples: pitch decks, product briefs, company overviews, marketing copy, data sheets.

**Supported formats:** PDF, DOCX, PPTX, XLSX, CSV, TXT, MD, JSON, HTML (max 256MB per file). Large files are chunked and indexed for semantic search; if a file exceeds the vector store's per-file token budget or AI processing otherwise rejects it, the file's `status` transitions to `"error"` and an `error` field on the file object describes the cause. Poll `GET /context/{id}/assets` or `GET /context/{id}` to read the status + error.

**Upload via file (recommended for agents):**
```bash
curl -X POST https://api.glxstudio.com/context \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@pitch-deck.pdf" \
  -F "name=Q1 Pitch Deck"
```

**Upload via URL:**
```bash
curl -X POST https://api.glxstudio.com/context \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Q1 Pitch Deck", "files": [{"url": "https://example.com/pitch-deck.pdf"}]}'
```

**Response (201):**
```json
{
  "contextStoreId": "ctx_42",
  "name": "Q1 Pitch Deck",
  "status": "processing",
  "fileCount": 1,
  "files": [
    { "fileId": 1, "filename": "pitch-deck.pdf", "status": "downloading" }
  ]
}
```

### Step 2: Wait for indexing to complete

Poll until `status` is `ready`. Typically takes 10-30 seconds.

```bash
curl https://api.glxstudio.com/context/ctx_42 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response when ready:**
```json
{
  "contextStoreId": "ctx_42",
  "name": "Q1 Pitch Deck",
  "status": "ready",
  "fileCount": 1,
  "files": [
    { "fileId": 1, "filename": "pitch-deck.pdf", "status": "ready", "fileSize": 2048576 }
  ]
}
```

If `status` is `error`, the file could not be processed. Ask the user to try a different file.

**If indexing takes longer than ~5 minutes:** the indexing queue is occasionally backed up, especially for larger files (50MB+). Don't burn the user's foreground attention on a tight poll loop. Tell them something like *"This is taking longer than usual — the indexing queue is busy. I'll keep checking in the background and let you know when it's ready."* Then switch to a longer poll interval (60-120s) and continue watching. Resume the conversation only when `status` flips to `ready` or `error`. If you're an agent that doesn't have a true background mode, ask the user whether they'd like to wait or come back later — don't silently spin.

### Step 3: Choose a template

List the organization's templates and let the user pick one:

```bash
curl https://api.glxstudio.com/templates \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response:**
```json
{
  "templates": [
    {
      "templateId": "tpl_7",
      "name": "Product Demo",
      "status": "active",
      "variables": {
        "productName": { "required": true, "description": "Name of the product" },
        "targetAudience": { "required": false, "default": "business professionals", "description": "Who the video is aimed at" }
      },
      "defaults": { "language": "en", "voiceId": "ZF6FPAbjXT4488VcRRnw", "mediaSourcePosition": 2 }
    }
  ]
}
```

Present template names to the user and let them choose. Then inspect the `variables` to know what inputs to collect.

For full template details including the prompt text:

```bash
curl https://api.glxstudio.com/templates/tpl_7 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Step 4: Collect variable values and start generation

Ask the user for values for each required variable. Optional variables will use their defaults if not provided.

```bash
curl -X POST https://api.glxstudio.com/storybuilder \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "tpl_7",
    "contextStoreId": "ctx_42",
    "variables": {
      "productName": "Widget Pro",
      "targetAudience": "Small business owners"
    }
  }'
```

**Optional parameters:**
- `callbackUrl` — Webhook URL to receive a POST when the video is done (or fails)
- `language` — Override the template's default language (ISO 639-1 code: "en", "es", "fr", "de", "it", "pt", "ja", "ko", "zh")
- `voiceId` — Override the template's default voice. Opaque ElevenLabs voice ID tagged with a single language in GLX Studio's curated collection.

**Voice & language rules:**

| You override… | Resolved voice |
| --- | --- |
| nothing | template's default voice, else default voice for the template's language |
| `language` only | default voice for the new language (template's voice is ignored — may not match) |
| `voiceId` only | your `voiceId` (validated against the template's language) |
| both | your `voiceId` (validated against your new `language`) |

Each voice is tagged with exactly one language. If you pass a `voiceId` that isn't tagged for the effective `language`, the API returns `400 VOICE_LANGUAGE_MISMATCH` — the request is rejected, not silently swapped.

Simplest rule for agents: if the user wants a different language, override `language` and leave `voiceId` alone. GLX Studio will pick the correct default voice. Only pass `voiceId` when the user explicitly picks a voice from the Developer Dashboard's Voice dropdown (that's where they discover valid voice IDs for each language).

**Response (202):**
```json
{
  "jobId": "sbv_123",
  "status": "processing"
}
```

### Step 5: Poll for completion

Poll every 15-30 seconds. Total time is typically 3-8 minutes.

Status progression: `processing` (2-5 min) → `rendering` (1-3 min) → `completed`

```bash
curl https://api.glxstudio.com/storybuilder/sbv_123 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response when completed:**
```json
{
  "jobId": "sbv_123",
  "status": "completed",
  "templateId": "tpl_7",
  "contextStoreId": "ctx_42",
  "createdAt": "2026-02-17T15:30:00Z",
  "title": "Widget Pro — Built for Small Business",
  "objective": "Position Widget Pro as the time-saving operations hub for small business owners — emphasize the 30-minute setup, the integrated dashboard, and the lift on margins from automated reporting.",
  "videoUrl": "https://d2sggu5v0gbmu6.cloudfront.net/...",
  "thumbnailUrl": "https://d2sggu5v0gbmu6.cloudfront.net/...",
  "duration": 62.5
}
```

**Response when failed:**
```json
{
  "jobId": "sbv_123",
  "status": "failed",
  "error": "Failed to generate creative brief from context documents"
}
```

### Step 6: Present the result

When `status` is `completed`:
- **`title`** — The video's headline title (initially the template name; replaced with the AI-generated title once the creative brief lands). Use this as the page heading.
- **`objective`** — The AI-generated "Objective of this video" from the creative brief — a one-paragraph summary of what the finished video is actually about (audience, tone, key messages). Only present once the brief has been built (i.e. once `status` advances past `processing`). Use it to style and frame any wrapper page so the visual treatment matches the video's purpose and any brand cues it mentions.
- **`videoUrl`** — Direct download link to the MP4 video. Valid for 24 hours. Can be opened in a browser, embedded in a `<video>` tag, or downloaded.
- **`thumbnailUrl`** — JPEG preview image. Valid for 24 hours. Show this as a visual preview.
- **`duration`** — Video length in seconds. Display as minutes:seconds (e.g., 62.5 → "1:02").

If the URL expires after 24 hours, poll `GET /storybuilder/{id}` again to get fresh pre-signed URLs.

### Step 7: Build a viewer page

Once you have the `videoUrl`, create a simple HTML page that plays the video and styles the surrounding page to match the `objective` (and any branding it mentions — colors, product name, audience tone). Use `thumbnailUrl` as the `<video poster>` so the page has a visual before playback starts. Open it in the user's browser so they can watch their video.

**Don't autoplay.** Set the `<video>` element with `controls` only — no `autoplay`. Modern browsers mute autoplaying video, which means the user would miss the narration on the first viewing. Let them click play.

**Deliver it the most direct way your environment supports** — render it inline if your host has a preview panel, otherwise save the file and open it via the OS, serve it over a quick local HTTP server, or share a path the user can open themselves. Pick one and ship it; don't ask the user to test or troubleshoot playback.

---

## Additional Endpoints

### List existing context stores

Check if a suitable context store already exists before creating a new one:

```bash
curl https://api.glxstudio.com/context \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Add files to an existing context store

Enrich an existing context store with additional documents:

```bash
curl -X POST https://api.glxstudio.com/context/ctx_42 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@additional-doc.pdf"
```

### Delete a context store

```bash
curl -X DELETE https://api.glxstudio.com/context/ctx_42 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

---

## Callback Webhook

Instead of polling, provide a `callbackUrl` when creating a job. The API will POST to your URL when the video is ready or fails.

**Success payload:**
```json
{
  "jobId": "sbv_123",
  "status": "completed",
  "videoUrl": "https://...",
  "thumbnailUrl": "https://...",
  "duration": 62.5
}
```

**Failure payload:**
```json
{
  "jobId": "sbv_123",
  "status": "failed",
  "error": "Error description"
}
```

Retry policy: 3 attempts with exponential backoff (5s, 30s, 120s). Return HTTP 2xx to acknowledge.

---

## Error Responses

All errors return JSON with `error` and `code` fields:

```json
{ "error": "Context store not found", "code": "NOT_FOUND" }
```

| HTTP Status | Meaning |
|---|---|
| 400 | Bad request — missing fields, invalid IDs, context store not ready, `VOICE_LANGUAGE_MISMATCH` |
| 401 | Unauthorized — missing or invalid Bearer token |
| 404 | Resource not found or does not belong to your organization |
| 429 | Rate limit exceeded — wait and retry |
| 500 | Server error — retry or contact support |

---

## Tips for AI Agents

- **Reuse context stores.** If the user has already uploaded a document, check `GET /context` before creating a new one.
- **Poll conservatively.** Every 15-30 seconds is sufficient. Avoid tight loops.
- **Show progress.** Tell the user generation takes 3-8 minutes. During `processing` the AI is writing the script; during `rendering` the video is being assembled.
- **Handle errors gracefully.** If a context store has `status: "error"`, suggest trying a different file. If a job fails, show the error message and offer to retry.
- **Duration formatting.** Convert seconds to mm:ss for display (e.g., `Math.floor(62.5/60)` + ":" + `Math.floor(62.5%60).toString().padStart(2,'0')` → "1:02").
- **URL expiration.** Video and thumbnail URLs expire after 24 hours. If presenting a result from a previous session, re-poll the job to get fresh URLs.
