> ## 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.

# Prepare Render

> Prepare a render and receive direct-upload plans for any media that needs uploading.

Starts the draft-based render flow. Instead of embedding media bytes in a JSON request, `prepare` returns short-lived direct-upload URLs; upload the character/motion bytes straight to those URLs, then call the `application/json` form of [Render Video (from Character and/or Motion)](/v1/api-reference/renders/create) with the returned `draft_id`.

## Request parameters

Send `application/json`.

| Parameter                                        | Type    |           Required          | Description                                                                                                                                                                       |
| ------------------------------------------------ | ------- | :-------------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `character`                                      | object  |             Yes             | Character source descriptor. Choose exactly one form: direct-upload metadata, a public HTTPS URL, or the ID of a ready reusable Character.                                        |
| `motion`                                         | object  |             Yes             | Motion source descriptor. Choose direct-upload metadata, a public HTTPS URL, a ready reusable Motion ID, or an official Viggle motion template ID.                                |
| `character.kind` / `motion.kind`                 | string  |             Yes             | Source discriminator: `upload`, `url`, or `asset`; the motion source also accepts `official_motion`. The selected value determines which sibling fields are required.             |
| `character.filename` / `motion.filename`         | string  |      With `kind=upload`     | Original filename including its extension. It is used to identify and validate the media in the subsequent direct upload.                                                         |
| `character.content_type` / `motion.content_type` | string  |      With `kind=upload`     | MIME type such as `image/png` or `video/mp4`. Send the same value in the direct-upload request's `Content-Type` header.                                                           |
| `character.bytes` / `motion.bytes`               | integer |      With `kind=upload`     | Exact file size in bytes, greater than zero. The uploaded object's size is checked against this declaration.                                                                      |
| `character.url` / `motion.url`                   | string  |       With `kind=url`       | Publicly reachable HTTPS URL of the source media. It must remain accessible while the draft is prepared and consumed.                                                             |
| `character.asset_id` / `motion.asset_id`         | string  |      With `kind=asset`      | ID of a ready reusable asset owned by the caller. Use a Character ID in `character` and a Motion ID in `motion`.                                                                  |
| `motion.motion_id`                               | string  | With `kind=official_motion` | Identifier of the official Viggle motion template to use as the driving motion. This form is available only for the motion slot.                                                  |
| `output`                                         | object  |              No             | Optional output configuration. Omit it to use the pipeline's default background treatment and source aspect ratio.                                                                |
| `output.background_mode`                         | string  |              No             | `original`, `green`, `white`, or `transparent`. `green`/`white` are translated to the pipeline's solid mode with the matching colour.                                             |
| `output.aspect_ratio`                            | string  |              No             | Only `source` is served today; `16:9`, `9:16`, and `1:1` are accepted values but currently rejected rather than silently ignored, since the render pipeline does not reframe yet. |

`kind: "upload"` declares an upload slot without sending bytes yet — supply the file's `filename`, `content_type`, and `bytes` size, then `PUT` the actual bytes to the URL returned below. Omitting `output` entirely uses the pipeline's default background and aspect ratio, same as omitting it on the direct `multipart/form-data` form of [Render Video (from Character and/or Motion)](/v1/api-reference/renders/create).

## Response parameters

Returns `200 OK`.

| Field                        | Type   | Always present | Description                                                                                                    |
| ---------------------------- | ------ | :------------: | -------------------------------------------------------------------------------------------------------------- |
| `draft_id`                   | string |       Yes      | Pass this to the JSON form of [Render Video (from Character and/or Motion)](/v1/api-reference/renders/create). |
| `state`                      | string |       Yes      | `awaiting_uploads` if any `kind: "upload"` source was declared, otherwise `ready_to_create`.                   |
| `uploads`                    | array  |       Yes      | One entry per declared upload slot. Empty when every source was `url`/`asset`/`official_motion`.               |
| `uploads[].slot`             | string |       Yes      | `character` or `motion`.                                                                                       |
| `uploads[].upload_handle`    | string |       Yes      | Echo this in `upload_completions` when creating the render.                                                    |
| `uploads[].method`           | string |       Yes      | Always `PUT`.                                                                                                  |
| `uploads[].url`              | string |       Yes      | Short-lived direct-upload URL.                                                                                 |
| `uploads[].required_headers` | object |       Yes      | Headers to send on the `PUT` request, as `{name: value}`.                                                      |
| `uploads[].expires_at`       | string |       Yes      | ISO 8601 expiry of the upload URL.                                                                             |

```json theme={null}
{
  "draft_id": "draft_abc123",
  "state": "awaiting_uploads",
  "uploads": [
    {
      "slot": "character",
      "upload_handle": "up_1",
      "method": "PUT",
      "url": "https://uploads.viggle.ai/draft_abc123/character?sig=...",
      "required_headers": { "Content-Type": "image/png" },
      "expires_at": "2026-07-31T09:30:00+00:00"
    }
  ]
}
```

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://apis.viggle.ai/v1/renders/prepare" \
    -H "Authorization: Bearer $VIGGLE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
          "character": {"kind":"upload","filename":"character.png","content_type":"image/png","bytes":204800},
          "motion": {"kind":"asset","asset_id":"mot_456def"}
        }'
  ```

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

  response = requests.post(
      "https://apis.viggle.ai/v1/renders/prepare",
      headers={"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"},
      json={
          "character": {"kind": "upload", "filename": "character.png", "content_type": "image/png", "bytes": 204800},
          "motion": {"kind": "asset", "asset_id": "mot_456def"},
      },
  )
  response.raise_for_status()
  draft = response.json()

  for upload in draft["uploads"]:
      with open("character.png", "rb") as f:
          put = requests.put(upload["url"], data=f, headers=upload["required_headers"])
      put.raise_for_status()
  ```
</CodeGroup>

## Next step

`PUT` the raw bytes of each declared source to its `uploads[].url` with `uploads[].required_headers`, before `expires_at`. Then call [Render Video (from Character and/or Motion)](/v1/api-reference/renders/create) with `Content-Type: application/json`, the returned `draft_id`, and an `upload_completions` entry per uploaded slot.


## OpenAPI

````yaml openapi.yaml POST /v1/renders/prepare
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:
  - bearerAuth: []
tags:
  - name: Renders
    description: Prepare inputs, create renders, observe progress, and retrieve results.
  - name: Credits
    description: Read the credit balance available to the calling principal.
  - name: Characters
    description: |
      Create, list, inspect, and delete reusable character assets. `type`
      selects whether creation also extracts a 3D vsplat; export its
      download URL separately once ready.
  - name: Motions
    description: |
      Create, list, inspect, and delete reusable motion assets, and import one
      from an official motion template. `type` selects whether creation also
      extracts or generates a 3D animation; export its download URL
      separately once ready.
  - name: Videos
    description: |
      Generate a MiniMax H3 text-to-video (`vid_` prefix), and read the
      unified view over every video the caller owns — H3 generations and
      character+motion Renders (`render_` prefix) — merged into one
      resource.
paths:
  /v1/renders/prepare:
    post:
      tags:
        - Renders
      summary: Prepare a render and receive direct-upload plans
      operationId: prepareRender
      parameters:
        - $ref: '#/components/parameters/RequestId'
        - $ref: '#/components/parameters/SourceChannel'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PrepareRenderRequest'
      responses:
        '200':
          description: Draft and any uploads required before render creation.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PrepareRenderResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/V1Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
components:
  parameters:
    RequestId:
      name: X-Request-Id
      in: header
      required: false
      description: >-
        Optional caller-supplied correlation ID, up to 128 characters. The
        service returns the effective value in the response header for tracing
        and support.
      schema:
        type: string
        minLength: 1
        maxLength: 128
    SourceChannel:
      name: X-Viggle-Source
      in: header
      required: false
      description: >-
        Optional source-channel label, up to 128 characters, used to attribute
        traffic to an SDK, integration, product surface, or internal workflow.
      schema:
        type: string
        minLength: 1
        maxLength: 128
  schemas:
    PrepareRenderRequest:
      type: object
      additionalProperties: false
      required:
        - character
        - motion
      properties:
        character:
          description: >-
            Character input for the render. Choose exactly one source
            form—direct upload metadata, a public HTTPS URL, or a reusable asset
            ID.
          allOf:
            - $ref: '#/components/schemas/CharacterSource'
        motion:
          description: >-
            Motion input for the render. Choose a direct upload, public HTTPS
            URL, reusable Motion ID, or official motion template ID.
          allOf:
            - $ref: '#/components/schemas/MotionSource'
        output:
          description: >-
            Optional output settings. Omitted fields use the service defaults
            described by `RenderOutputOptions`.
          allOf:
            - $ref: '#/components/schemas/RenderOutputOptions'
    PrepareRenderResponse:
      type: object
      additionalProperties: false
      required:
        - draft_id
        - state
        - uploads
      properties:
        draft_id:
          type: string
          description: >-
            Opaque prepared-render draft identifier. Pass it unchanged to `POST
            /v1/renders` after completing any required uploads.
          minLength: 1
        state:
          type: string
          description: >-
            Whether media uploads must be completed before creation.
            `ready_to_create` means the draft can be submitted immediately.
          enum:
            - awaiting_uploads
            - ready_to_create
        uploads:
          type: array
          description: >-
            Direct-upload plans for inputs declared with `kind: upload`; empty
            when every source is already available to the service.
          items:
            $ref: '#/components/schemas/UploadPlan'
    CharacterSource:
      oneOf:
        - $ref: '#/components/schemas/UploadSource'
        - $ref: '#/components/schemas/UrlSource'
        - $ref: '#/components/schemas/AssetSource'
    MotionSource:
      oneOf:
        - $ref: '#/components/schemas/UploadSource'
        - $ref: '#/components/schemas/UrlSource'
        - $ref: '#/components/schemas/AssetSource'
        - $ref: '#/components/schemas/OfficialMotionSource'
    RenderOutputOptions:
      type: object
      additionalProperties: false
      properties:
        background_mode:
          description: >
            Controls how the background is produced. `original` preserves or
            reconstructs the source background; `green` and `white` are
            translated to the pipeline's solid mode with the matching colour;
            `transparent` requests a separate alpha/mask output for compositing.
          type: string
          enum:
            - original
            - green
            - white
            - transparent
        aspect_ratio:
          description: >
            Only `source` is served today. The render pipeline does not reframe
            yet, so any other value is rejected rather than silently ignored.
          type: string
          enum:
            - source
            - '16:9'
            - '9:16'
            - '1:1'
    UploadPlan:
      type: object
      additionalProperties: false
      required:
        - slot
        - upload_handle
        - method
        - url
        - required_headers
        - expires_at
      properties:
        slot:
          type: string
          description: >-
            Input slot this upload satisfies. Upload the corresponding character
            image or motion video to the supplied URL.
          enum:
            - character
            - motion
        upload_handle:
          type: string
          description: >-
            Opaque identifier for this planned upload. Return it in
            `upload_completions` when creating the render.
          minLength: 1
        method:
          type: string
          description: HTTP method required for the direct upload; currently always `PUT`.
          enum:
            - PUT
        url:
          type: string
          format: uri
          description: >-
            Short-lived presigned URL to which the media bytes must be uploaded
            directly.
        required_headers:
          type: object
          description: >-
            Exact HTTP headers that must be included with the direct upload,
            commonly including the declared content type.
          additionalProperties:
            type: string
        expires_at:
          type: string
          format: date-time
          description: >-
            ISO 8601 expiration time of the presigned upload URL. Prepare a new
            draft if the upload cannot finish before this time.
    ErrorResponse:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          description: >-
            Top-level draft-flow error payload containing the stable code,
            message, retry guidance, correlation ID, and error-specific details.
          allOf:
            - $ref: '#/components/schemas/ErrorBody'
    UploadSource:
      type: object
      additionalProperties: false
      required:
        - kind
        - filename
        - content_type
        - bytes
      properties:
        kind:
          type: string
          description: >-
            Source discriminator. Set to `upload` when the media will be
            uploaded through the returned direct-upload plan.
          enum:
            - upload
        filename:
          type: string
          description: >-
            Original filename, including its extension, used to identify and
            validate the uploaded media.
          minLength: 1
        content_type:
          type: string
          description: >-
            MIME type of the file to upload, such as `image/png` or `video/mp4`;
            it must match the upload request header.
          minLength: 1
        bytes:
          type: integer
          format: int64
          description: >-
            Exact file size in bytes. The uploaded object's size is checked
            against this declaration.
          minimum: 1
    UrlSource:
      type: object
      additionalProperties: false
      required:
        - kind
        - url
      properties:
        kind:
          type: string
          description: >-
            Source discriminator. Set to `url` when Viggle should fetch the
            media from a remote HTTPS address.
          enum:
            - url
        url:
          type: string
          format: uri
          description: >-
            Publicly reachable HTTPS URL of the source media. The URL must
            remain accessible while the render is being prepared.
          pattern: ^https://
    AssetSource:
      type: object
      additionalProperties: false
      required:
        - kind
        - asset_id
      properties:
        kind:
          type: string
          description: >-
            Source discriminator. Set to `asset` to reuse a character or motion
            already stored in the caller's account.
          enum:
            - asset
        asset_id:
          type: string
          description: >-
            ID of a ready reusable asset. Use a Character ID for the character
            slot and a Motion ID for the motion slot.
          minLength: 1
    OfficialMotionSource:
      type: object
      additionalProperties: false
      required:
        - kind
        - motion_id
      properties:
        kind:
          type: string
          description: >-
            Source discriminator. Set to `official_motion` to render with a
            motion from Viggle's official template catalog.
          enum:
            - official_motion
        motion_id:
          type: string
          description: >-
            Identifier of the official motion template to use as the driving
            motion.
          minLength: 1
    ErrorBody:
      type: object
      additionalProperties: false
      required:
        - code
        - message
        - retryable
        - request_id
        - details
        - remediation
      properties:
        code:
          description: Stable error category from the draft-based render error vocabulary.
          allOf:
            - $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
          description: >-
            Human-readable explanation suitable for logs or presentation to a
            developer.
        retryable:
          type: boolean
          description: >-
            Whether retrying the operation without changing its semantic input
            may succeed.
        request_id:
          type: string
          description: >-
            Correlation identifier for tracing and support. Include it when
            reporting the failure.
        details:
          type: object
          description: Error-specific structured context; available keys depend on `code`.
          additionalProperties: true
        remediation:
          description: Suggested recovery action and optional retry delay.
          allOf:
            - $ref: '#/components/schemas/ErrorRemediation'
    ErrorCode:
      description: |
        The failure vocabulary of the draft-based render flow. The resource
        operations project the same vocabulary onto the lower_snake_case codes
        described by `ResourceError`.
      type: string
      enum:
        - UNAUTHENTICATED
        - INVALID_CREDENTIAL
        - FORBIDDEN
        - INVALID_REQUEST
        - INVALID_CHARACTER
        - INVALID_MOTION
        - UNSUPPORTED_MEDIA
        - CONTENT_POLICY_VIOLATION
        - UPLOAD_REQUIRED
        - UPLOAD_MISMATCH
        - UPLOAD_EXPIRED
        - DRAFT_NOT_FOUND
        - DRAFT_ALREADY_CONSUMED
        - RENDER_NOT_FOUND
        - RENDER_NOT_CANCELLABLE
        - CHARACTER_NOT_FOUND
        - MOTION_NOT_FOUND
        - MOTION_NOT_READY
        - MOTION_TEMPLATE_NOT_FOUND
        - AVATAR_NOT_FOUND
        - ANIMATION_NOT_FOUND
        - EXTRACTION_UNAVAILABLE
        - EXTRACTION_FAILED
        - IDEMPOTENCY_KEY_REUSED
        - INSUFFICIENT_CREDITS
        - RATE_LIMITED
        - SERVICE_BUSY
        - INTERNAL_ERROR
    ErrorRemediation:
      type: object
      additionalProperties: false
      required:
        - action
        - retry_after_ms
      properties:
        action:
          type: string
          description: >-
            Suggested machine-readable or human-readable next action, such as
            retrying later, uploading media again, or correcting input.
        retry_after_ms:
          type: integer
          description: >-
            Recommended delay in milliseconds before retrying; null when no
            timed retry is advised.
          nullable: true
          format: int64
          minimum: 0
  headers:
    RequestId:
      description: Stable request identifier for support and tracing.
      schema:
        type: string
  responses:
    BadRequest:
      description: Invalid request.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    V1Unauthorized:
      description: Missing, invalid, or expired credential.
      headers:
        WWW-Authenticate:
          schema:
            type: string
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: Credential does not grant access to this resource.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    UnprocessableEntity:
      description: Media or content policy rejection.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Rate limit exceeded.
      headers:
        Retry-After:
          schema:
            type: integer
            minimum: 0
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalServerError:
      description: Unexpected server error.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ServiceUnavailable:
      description: Service temporarily unavailable.
      headers:
        Retry-After:
          schema:
            type: integer
            minimum: 0
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key or OAuth access token
      description: |
        Server-side SDK clients use a project API key. Remote MCP clients use
        an OAuth access token. Never expose a project API key in browser code.

````