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

# Extract Motion (dynamic-fbx)

> Extract an animation from a motion video

Upload a motion video and get the animation back as `animation.glb` + `animation.fbx`. Send it as `multipart/form-data`.

<Note>
  **Asynchronous task.** This endpoint returns a `task_id` immediately and does not block. Poll [Get Extract Task](/api-reference/extract/status) until `status` is `complete`.

  State machine: `queued` → `processing` → `complete` | `failed`
</Note>

<Note>
  **Binary upload.** Send the raw file directly as `multipart/form-data` — no URL or storage path is required.
</Note>

<Warning>
  **Video limits:** uploads are limited to **100 MB** file size and **0.5–600 seconds** (10 minutes) duration. Resolution must be at least **64×64** with **even** width and height. Files outside these limits are rejected with `400 CS-UORC01-002`.
</Warning>

## Parameters

Send as `multipart/form-data`.

| Parameter          | Type    | Required | Default | Description                                                                |
| ------------------ | ------- | :------: | ------- | -------------------------------------------------------------------------- |
| `video`            | file    |     ✅    | —       | Motion-source video (MP4, MOV, WebM). See limits above.                    |
| `enable_smoothing` | boolean |          | `false` | Enable motion smoothing on the extracted animation.                        |
| `target_fps`       | number  |          | —       | Target frame rate (must be > 0). Omit to skip resampling.                  |
| `task_id`          | string  |          | —       | Optional client-supplied id for [idempotent retries](#idempotent-retries). |

## Pricing

Motion extraction is billed by input video duration: **5 credits per second**, rounded up to the whole second. Credits are reserved when the task is created, settled when the task completes successfully, and automatically refunded if the task fails. Insufficient balance returns `402 CS-UORC01-008`.

## Idempotent retries

The `task_id` form field is optional. Supplying your own `task_id` enables idempotent retries: re-creating with the same `task_id` returns `409 CS-UORC01-015`. If omitted, the server generates one (format `ext_` + 16 hex chars).

## Response

```json theme={null}
{
  "task_id": "ext_0f9e8d7c6b5a4332",
  "kind": "dynamic_fbx",
  "workflow_id": "uorc-extract-ext_0f9e8d7c6b5a4332",
  "status": "queued"
}
```

`workflow_id` is an internal processing identifier — clients can ignore it and poll using `task_id`.

## Error Handling

If the request fails, the response includes a structured error with an `error_code`:

```json theme={null}
{
  "detail": {
    "error_code": "CS-UORC01-002",
    "error": "invalid_request",
    "message": "target_fps must be > 0."
  }
}
```

Common error codes for this endpoint:

| Code            | Cause                                             | Fix                                       |
| --------------- | ------------------------------------------------- | ----------------------------------------- |
| `CS-UORC01-006` | Missing API key                                   | Add `Authorization: Bearer sk-...` header |
| `CS-UORC01-007` | Invalid or expired API key                        | Check your key is correct                 |
| `CS-UORC01-002` | Validation failed (empty file, `target_fps <= 0`) | Check your form fields                    |
| `CS-UORC01-008` | Insufficient credits                              | Top up credits                            |
| `CS-UORC01-015` | `task_id` already in use                          | Use a new `task_id`                       |
| `CS-UORC01-016` | Extract service temporarily unavailable           | Retry in a few seconds (HTTP 502)         |

See [Error Codes](/error-codes) for the full list. When contacting **[support@viggle.ai](mailto:support@viggle.ai)**, include the `error_code` and your `task_id`.

## Examples

<CodeGroup>
  ```python Python theme={null}
  import requests

  url = "https://apis.viggle.ai/api/extract/dynamic-fbx"
  headers = {"Authorization": "Bearer YOUR_API_KEY"}

  with open("dance.mp4", "rb") as vid:
      resp = requests.post(
          url,
          headers=headers,
          files={"video": ("dance.mp4", vid, "video/mp4")},
          data={"enable_smoothing": "true", "target_fps": "30"},
      )

  data = resp.json()
  if "detail" in data:
      print(f"Error {data['detail']['error_code']}: {data['detail']['message']}")
  else:
      print(f"Task created: {data['task_id']}")
      print(f"Poll: GET /api/extract/{data['task_id']}")
  ```

  ```javascript JavaScript theme={null}
  const form = new FormData();
  form.append("video", await fetch("dance.mp4").then(r => r.blob()), "dance.mp4");
  form.append("enable_smoothing", "true");
  form.append("target_fps", "30");

  const resp = await fetch("https://apis.viggle.ai/api/extract/dynamic-fbx", {
    method: "POST",
    headers: { "Authorization": "Bearer YOUR_API_KEY" },
    body: form,
  });

  const data = await resp.json();
  if (data.detail) {
    console.error(`Error ${data.detail.error_code}: ${data.detail.message}`);
  } else {
    console.log(`Task created: ${data.task_id}`);
  }
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://apis.viggle.ai/api/extract/dynamic-fbx" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -F "video=@dance.mp4" \
    -F "enable_smoothing=true" \
    -F "target_fps=30"
  ```
</CodeGroup>

## Next Steps

After creating the task, poll [Get Extract Task](/api-reference/extract/status) every 5-10 seconds. When `status` is `complete`, read the signed download links from `result` (`glb_url`, `fbx_url`).


## OpenAPI

````yaml POST /api/extract/dynamic-fbx
openapi: 3.0.3
info:
  title: Viggle API
  description: Generate AI-powered character animation videos
  version: 2.0.0
  contact:
    name: Viggle Support
    url: https://viggle.ai
servers:
  - url: https://apis.viggle.ai
    description: Production server
security: []
paths:
  /api/extract/dynamic-fbx:
    post:
      tags:
        - Extract
      summary: Extract Motion (dynamic-fbx)
      description: >-
        Upload a motion video and extract an animation as animation.glb +
        animation.fbx. Asynchronous: returns a task_id immediately — poll GET
        /api/extract/{task_id} until status is complete. Billed by input video
        duration at 5 credits per second, rounded up to the whole second
        (reserved on create, settled on success, refunded on failure).
      operationId: extractDynamicFbx
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - video
              properties:
                video:
                  type: string
                  format: binary
                  description: Input video file (motion source). MP4, MOV, WebM, etc.
                task_id:
                  type: string
                  description: >-
                    Optional client-supplied task id for idempotent retries.
                    Reusing an existing id returns 409. If omitted, the server
                    generates one (format ext_ + 16 hex chars).
                  example: ext_0f9e8d7c6b5a4332
                enable_smoothing:
                  type: boolean
                  default: false
                  description: Whether to enable motion smoothing.
                target_fps:
                  type: number
                  format: float
                  description: Target frame rate. Must be > 0. Omit to skip resampling.
                  example: 30
      responses:
        '200':
          description: Extract task created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExtractCreated'
        '400':
          description: Validation failed (e.g. empty file, target_fps <= 0)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorStructured'
              example:
                detail:
                  error_code: CS-UORC01-002
                  message: >-
                    Validation failed. Check that the video file is present and
                    target_fps is greater than 0.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '409':
          $ref: '#/components/responses/TaskConflict'
        '502':
          $ref: '#/components/responses/ExtractServiceUnavailable'
      security:
        - bearerAuth: []
components:
  schemas:
    ExtractCreated:
      type: object
      properties:
        task_id:
          type: string
          example: ext_a1b2c3d4e5f60718
          description: Task id. Use it to poll GET /api/extract/{task_id}.
        kind:
          type: string
          enum:
            - static_bin
            - dynamic_fbx
          description: Extract task kind.
          example: static_bin
        workflow_id:
          type: string
          description: >-
            Internal processing identifier. Clients can ignore it — poll with
            task_id.
          example: uorc-extract-ext_a1b2c3d4e5f60718
        status:
          type: string
          example: queued
    ErrorStructured:
      type: object
      description: >-
        Structured error. `detail` carries a machine-readable `error_code` plus
        a human-readable `message`.
      properties:
        detail:
          type: object
          properties:
            error_code:
              type: string
              example: CS-UORC01-008
            message:
              type: string
              example: Insufficient credits. Top up your balance to continue.
    Error:
      type: object
      description: >-
        Simple error. `detail` is a human-readable message (used for auth
        errors).
      properties:
        detail:
          type: string
          example: 'API key required. Use Authorization: Bearer sk-xxx'
  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            detail: >-
              Invalid or missing API key. Pass it as Authorization: Bearer
              sk-...
    InsufficientCredits:
      description: Insufficient credits
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorStructured'
          example:
            detail:
              error_code: CS-UORC01-008
              message: Insufficient credits. Top up your balance to continue.
    TaskConflict:
      description: A task with this task_id already exists
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorStructured'
          example:
            detail:
              error_code: CS-UORC01-015
              message: A task with this id already exists. Use a new task_id.
    ExtractServiceUnavailable:
      description: Extract service temporarily unavailable
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorStructured'
          example:
            detail:
              error_code: CS-UORC01-016
              message: >-
                Extract service is temporarily unavailable. Retry in a few
                seconds.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key passed as Bearer token

````