> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yuzulabs.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Sales Email Video Flow

> Generate a 60–70 second branded video from a sales-call summary email.

The Sales Email Video Flow converts a sales-call summary into a short follow-up video featuring an AI-generated script, voice narration, and avatar render. Flows run asynchronously through a sequence of states.

**Base URL**: `https://app.yuzulabs.io/api/v1`

All endpoints require [Bearer authentication](/api-reference/introduction#authentication) and a workspace with at least one ready avatar, a configured voice, and remaining quota.

## Lifecycle

```
pending → generating_script → [pending_approval] → segmenting → voice_generating → video_generating → completed
```

| Status              | Description                                                                      |
| ------------------- | -------------------------------------------------------------------------------- |
| `pending`           | Flow accepted, queued.                                                           |
| `generating_script` | Drafting the video script.                                                       |
| `pending_approval`  | Awaiting a human approve / edit / reject (only when `autoApproveScript: false`). |
| `segmenting`        | Breaking the script into scenes.                                                 |
| `voice_generating`  | Producing the voiceover.                                                         |
| `video_generating`  | Rendering the avatar + scenes.                                                   |
| `completed`         | `videoUrl` available.                                                            |
| `failed`            | Permanent error — see `error`.                                                   |
| `cancelled`         | Cancelled by user or timeout.                                                    |

## Endpoint summary

| Method   | Path                                           | Scope           |
| -------- | ---------------------------------------------- | --------------- |
| `POST`   | `/v1/flows/sales-email-video`                  | `flows:create`  |
| `GET`    | `/v1/flows/sales-email-video`                  | `flows:read`    |
| `GET`    | `/v1/flows/sales-email-video/{flowId}`         | `flows:read`    |
| `POST`   | `/v1/flows/sales-email-video/{flowId}/approve` | `flows:approve` |
| `DELETE` | `/v1/flows/sales-email-video/{flowId}`         | `flows:cancel`  |

***

## `POST` Create flow

`POST /v1/flows/sales-email-video`

Creates a flow and starts script generation. The response returns immediately with a `flowId` you can poll.

### Body parameters

<ParamField body="emailContent" type="string" required>
  Sales-call summary in plain text. Minimum 50 words. Becomes the input for script generation.
</ParamField>

<ParamField body="autoApproveScript" type="boolean" default="true">
  When `true`, the flow proceeds straight from `generating_script` to `segmenting`. When `false`, it stops at `pending_approval` until you call the `/approve` endpoint.
</ParamField>

<ParamField body="avatarId" type="string">
  Convex ID of the avatar to render. Defaults to the first ready 16:9 avatar in your workspace.
</ParamField>

<ParamField body="voiceId" type="string">
  ElevenLabs voice ID. Defaults to the workspace's configured voice.
</ParamField>

<ParamField body="prospectName" type="string">
  Prospect's first name. Used in the script's greeting.
</ParamField>

<ParamField body="prospectCompany" type="string">
  Prospect's company name.
</ParamField>

<ParamField body="userName" type="string">
  Sender's name. Defaults to the API key's owner.
</ParamField>

<ParamField body="userCompany" type="string">
  Sender's company. Defaults to the workspace name.
</ParamField>

<ParamField body="title" type="string">
  Video title shown in the dashboard. Auto-generated from the prospect name when omitted.
</ParamField>

### Response

<ResponseField name="success" type="boolean" required>
  `true` on accepted requests.
</ResponseField>

<ResponseField name="flowId" type="string" required>
  Convex ID. Use this to poll status, approve, or cancel.
</ResponseField>

<ResponseField name="status" type="string" required>
  Always `pending` for newly created flows.
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable confirmation.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://app.yuzulabs.io/api/v1/flows/sales-email-video \
    -H "Authorization: Bearer yzlk_your_api_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "emailContent": "Great call with John from Acme Corp today. We discussed video marketing pain points: production time, brand consistency, scaling content. Next step: send a product demo.",
      "autoApproveScript": true,
      "prospectName": "John",
      "prospectCompany": "Acme Corp"
    }'
  ```

  ```ts TypeScript theme={null}
  const res = await fetch("https://app.yuzulabs.io/api/v1/flows/sales-email-video", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.YUZU_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      emailContent: "Great call with John from Acme Corp today...",
      autoApproveScript: true,
      prospectName: "John",
      prospectCompany: "Acme Corp",
    }),
  });
  const { flowId } = await res.json();
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.post(
      "https://app.yuzulabs.io/api/v1/flows/sales-email-video",
      headers={"Authorization": f"Bearer {os.environ['YUZU_API_KEY']}"},
      json={
          "emailContent": "Great call with John from Acme Corp today...",
          "autoApproveScript": True,
          "prospectName": "John",
          "prospectCompany": "Acme Corp",
      },
  )
  flow_id = res.json()["flowId"]
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "success": true,
    "flowId": "k97x3n2m4p5q6r7s",
    "status": "pending",
    "message": "Flow created successfully"
  }
  ```

  ```json 400 Validation error theme={null}
  {
    "success": false,
    "error": "Email content must be at least 50 words",
    "code": "VALIDATION_ERROR"
  }
  ```

  ```json 429 Quota exceeded theme={null}
  {
    "success": false,
    "error": "Video quota exceeded",
    "code": "QUOTA_EXCEEDED"
  }
  ```
</ResponseExample>

***

## `GET` List flows

`GET /v1/flows/sales-email-video`

Returns flows visible to the API key, most recent first. Useful for dashboards and reconciliation jobs.

### Query parameters

<ParamField query="limit" type="number" default="20">
  Page size. Maximum 100.
</ParamField>

<ParamField query="cursor" type="string">
  Continuation cursor from a previous response.
</ParamField>

<ParamField query="status" type="string">
  Filter by a single status (e.g. `completed`, `pending_approval`).
</ParamField>

### Response

<ResponseField name="success" type="boolean" required />

<ResponseField name="flows" type="object[]" required>
  Array of flow summaries. Each entry contains `flowId`, `status`, `createdAt`, `title`, and `videoUrl` (when completed).
</ResponseField>

<ResponseField name="nextCursor" type="string">
  Present when more results are available.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl "https://app.yuzulabs.io/api/v1/flows/sales-email-video?limit=20&status=completed" \
    -H "Authorization: Bearer yzlk_your_api_key_here"
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "success": true,
    "flows": [
      {
        "flowId": "k97x3n2m4p5q6r7s",
        "status": "completed",
        "createdAt": "2026-01-22T10:30:00Z",
        "title": "Acme Corp follow-up",
        "videoUrl": "https://assets.yuzulabs.io/videos/k97x3n2m4p5q6r7s.mp4"
      }
    ],
    "nextCursor": null
  }
  ```
</ResponseExample>

***

## `GET` Get flow

`GET /v1/flows/sales-email-video/{flowId}`

Returns the current status of a single flow. Poll this every few seconds while the flow is in progress.

### Path parameters

<ParamField path="flowId" type="string" required>
  The flow ID returned from the create endpoint.
</ParamField>

### Response

<ResponseField name="flowId" type="string" required />

<ResponseField name="status" type="string" required>
  One of the lifecycle states above.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  ISO-8601 timestamp.
</ResponseField>

<ResponseField name="completedAt" type="string">
  ISO-8601 timestamp. Only present once `status` is `completed`.
</ResponseField>

<ResponseField name="videoUrl" type="string">
  Direct CDN URL to the rendered MP4. Only present once `status` is `completed`.
</ResponseField>

<ResponseField name="videoDuration" type="number">
  Duration in seconds.
</ResponseField>

<ResponseField name="generatedScript" type="string">
  Final script. Available from `pending_approval` onwards.
</ResponseField>

<ResponseField name="scenes" type="object[]">
  Per-scene breakdown: `index`, `text`, `durationEstimate`. Available from `segmenting` onwards.
</ResponseField>

<ResponseField name="error" type="string">
  Failure reason. Only present when `status` is `failed`.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl https://app.yuzulabs.io/api/v1/flows/sales-email-video/k97x3n2m4p5q6r7s \
    -H "Authorization: Bearer yzlk_your_api_key_here"
  ```

  ```ts TypeScript theme={null}
  const res = await fetch(
    `https://app.yuzulabs.io/api/v1/flows/sales-email-video/${flowId}`,
    { headers: { Authorization: `Bearer ${process.env.YUZU_API_KEY}` } },
  );
  const flow = await res.json();
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK (completed) theme={null}
  {
    "flowId": "k97x3n2m4p5q6r7s",
    "status": "completed",
    "createdAt": "2026-01-22T10:30:00Z",
    "completedAt": "2026-01-22T10:38:00Z",
    "videoUrl": "https://assets.yuzulabs.io/videos/k97x3n2m4p5q6r7s.mp4",
    "videoDuration": 65.2,
    "generatedScript": "Hey John, great speaking with you today...",
    "scenes": [
      { "index": 0, "text": "Hey John...", "durationEstimate": 12.5 },
      { "index": 1, "text": "I understand you're facing...", "durationEstimate": 15.0 }
    ]
  }
  ```

  ```json 200 OK (in progress) theme={null}
  {
    "flowId": "k97x3n2m4p5q6r7s",
    "status": "voice_generating",
    "createdAt": "2026-01-22T10:30:00Z",
    "generatedScript": "Hey John, great speaking with you today..."
  }
  ```

  ```json 404 Not found theme={null}
  {
    "success": false,
    "error": "Flow not found",
    "code": "FLOW_NOT_FOUND"
  }
  ```
</ResponseExample>

***

## `POST` Approve, edit, or reject script

`POST /v1/flows/sales-email-video/{flowId}/approve`

Only valid while the flow is in `pending_approval`. Use it to advance, edit, or kill a draft script.

### Path parameters

<ParamField path="flowId" type="string" required />

### Body parameters

<ParamField body="action" type="string" required>
  Either `"approve"` or `"reject"`.
</ParamField>

<ParamField body="editedScript" type="string">
  Replacement script. Honored only when `action: "approve"`. If omitted, the draft script proceeds unchanged.
</ParamField>

### Response

<ResponseField name="success" type="boolean" required />

<ResponseField name="flowId" type="string" required />

<ResponseField name="status" type="string" required>
  After approval, transitions to `segmenting`. After rejection, transitions to `cancelled`.
</ResponseField>

<RequestExample>
  ```bash Approve as-is theme={null}
  curl -X POST https://app.yuzulabs.io/api/v1/flows/sales-email-video/k97x3n2m4p5q6r7s/approve \
    -H "Authorization: Bearer yzlk_your_api_key_here" \
    -H "Content-Type: application/json" \
    -d '{"action": "approve"}'
  ```

  ```bash Approve with edits theme={null}
  curl -X POST https://app.yuzulabs.io/api/v1/flows/sales-email-video/k97x3n2m4p5q6r7s/approve \
    -H "Authorization: Bearer yzlk_your_api_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "action": "approve",
      "editedScript": "Hey John, picking up where we left off..."
    }'
  ```

  ```bash Reject theme={null}
  curl -X POST https://app.yuzulabs.io/api/v1/flows/sales-email-video/k97x3n2m4p5q6r7s/approve \
    -H "Authorization: Bearer yzlk_your_api_key_here" \
    -H "Content-Type: application/json" \
    -d '{"action": "reject"}'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "success": true,
    "flowId": "k97x3n2m4p5q6r7s",
    "status": "segmenting"
  }
  ```

  ```json 400 Wrong state theme={null}
  {
    "success": false,
    "error": "Flow is not awaiting approval (current status: video_generating)",
    "code": "VALIDATION_ERROR"
  }
  ```
</ResponseExample>

***

## `DELETE` Cancel flow

`DELETE /v1/flows/sales-email-video/{flowId}`

Cancels a flow in any non-terminal state. Already-rendered videos are kept; in-flight rendering is aborted.

### Path parameters

<ParamField path="flowId" type="string" required />

### Response

<ResponseField name="success" type="boolean" required />

<ResponseField name="flowId" type="string" required />

<ResponseField name="status" type="string" required>
  Always `cancelled` on success.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X DELETE https://app.yuzulabs.io/api/v1/flows/sales-email-video/k97x3n2m4p5q6r7s \
    -H "Authorization: Bearer yzlk_your_api_key_here"
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "success": true,
    "flowId": "k97x3n2m4p5q6r7s",
    "status": "cancelled"
  }
  ```
</ResponseExample>
