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

> ## Agent Instructions
> Agents: start with https://docs.pre.dev/agents.md, which has complete recipes, plan access, polling rules, errors and limits.
> Authenticate with the workspace API key (pdk_…) from Integrations → Built-in, sent as Authorization: Bearer <key>.
> REST API: https://api.pre.dev (OpenAPI: https://docs.pre.dev/api-reference/openapi.json). AI Gateway: https://api.pre.dev/v1, OpenAI-compatible (OpenAPI: https://docs.pre.dev/api-reference/ai-gateway.openapi.json).
> pre.dev MCP server: https://api.pre.dev/mcp. Search these docs over MCP at https://docs.pre.dev/mcp.

# Submit a video job

> Start an asynchronous video job, poll it by its id, then download the video.



## OpenAPI

````yaml api-reference/ai-gateway.openapi.json POST /v1/videos
openapi: 3.1.0
info:
  title: pre.dev AI Gateway API
  version: 1.0.0
  description: >-
    One OpenAI-compatible API for hundreds of models from every major lab, paid
    for in pre.dev credits.


    - **Base URL:** `https://api.pre.dev/v1` for the OpenAI SDKs. The Anthropic
    SDKs take `https://api.pre.dev` and add `/v1/messages` themselves.

    - **Auth:** the workspace API key (`pdk_…`) from Integrations → Built-in in
    the pre.dev dashboard, as `Authorization: Bearer <key>` or `x-api-key`. Keep
    it on a server: it spends the workspace's credits.

    - **Models:** send ids exactly as `GET /v1/models` lists them, such as
    `anthropic/claude-sonnet-5`.

    - **Public directory:** `GET /v1/models/public` lists every model with no
    key and no prices.

    - **Charges:** each call is charged in credits from its metered cost.
    Non-streaming responses report the charge in `x-predev-credits-charged`;
    `GET /v1/generation` reports it for any call. A request is refused with
    `402` before it runs when the balance cannot cover it. Catalog, usage,
    generation and file reads are free.

    - **Limits:** 600 requests per minute per workspace, 30 on the free trial. A
    free workspace can spend 5 credits on AI calls in total, then gets `402
    subscription_required`. Reads other than `GET /v1/files` do not count toward
    the per-minute limit.

    - **Errors:** errors raised by pre.dev use the OpenAI error shape with a
    `predev` object (`GatewayError`). Errors from the model keep their HTTP
    status and message (`ModelError`). Every inference response carries
    `x-predev-request-id`.

    - **Streaming:** `stream: true` returns server-sent events in the native
    format of each endpoint. Lines that start with `:` are keep-alives.


    Example values are illustrative.
  contact:
    name: pre.dev Support
    url: https://pre.dev
    email: support@pre.dev
  license:
    name: Proprietary
    url: https://pre.dev/terms
servers:
  - url: https://api.pre.dev
    description: Production
security:
  - apiKeyAuth: []
  - xApiKey: []
tags:
  - name: Chat
    description: Chat completions, text completions, Responses and Messages.
  - name: Embeddings
    description: Embeddings and rerank.
  - name: Images and video
    description: Image generation and asynchronous video jobs.
  - name: Audio
    description: Speech, music and transcription.
  - name: Files
    description: Documents stored for later chat requests.
  - name: Models
    description: The model catalog, with credit prices.
  - name: Usage
    description: Usage totals and per-call stats.
paths:
  /v1/videos:
    post:
      tags:
        - Images and video
      summary: Submit a video job
      description: >-
        Starts an asynchronous video job. Submitting needs a balance of at least
        10 credits.


        1. `POST /v1/videos` returns the job with its `id`, `status: "pending"`,
        and a `polling_url`.

        2. Poll `GET /v1/videos/{id}` (the `polling_url`) every few seconds
        until `status` is `completed`, `failed`, `cancelled` or `expired`.

        3. Download the video from `GET /v1/videos/{id}/content`.


        The job is charged once, when it finishes: on the first poll that
        reports completion, on the download if you never polled, or by pre.dev
        shortly after if you stopped polling.
      operationId: createVideo
      parameters:
        - $ref: '#/components/parameters/ProjectId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VideoRequest'
            example:
              model: bytedance/seedance-2.0-mini
              prompt: A paper boat drifting down a rain gutter
              duration: 4
              resolution: 480p
      responses:
        '202':
          description: The job was accepted.
          headers:
            x-predev-request-id:
              $ref: '#/components/headers/RequestId'
            x-predev-credits-charged:
              $ref: '#/components/headers/CreditsCharged'
            x-predev-credits-remaining:
              $ref: '#/components/headers/CreditsRemaining'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoJob'
              example:
                id: YCXWQa200sQEi5GSnlfP
                polling_url: https://api.pre.dev/v1/videos/YCXWQa200sQEi5GSnlfP
                status: pending
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '404':
          $ref: '#/components/responses/ModelNotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '502':
          $ref: '#/components/responses/ModelUnavailable'
        '503':
          $ref: '#/components/responses/BalanceUnavailable'
      x-codeSamples:
        - lang: cURL
          label: cURL (submit, poll, download)
          source: >-
            JOB=$(curl --fail-with-body -s https://api.pre.dev/v1/videos \
              -H "Authorization: Bearer $PREDEV_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{"model": "bytedance/seedance-2.0-mini", "prompt": "A paper boat drifting down a rain gutter", "duration": 4, "resolution": "480p"}')
            ID=$(echo "$JOB" | jq -r .id)


            while :; do
              STATUS=$(curl --fail-with-body -s "https://api.pre.dev/v1/videos/$ID" -H "Authorization: Bearer $PREDEV_API_KEY" | jq -r .status)
              case "$STATUS" in completed|failed|cancelled|expired) break ;; esac
              sleep 5
            done


            curl --fail-with-body "https://api.pre.dev/v1/videos/$ID/content" -H
            "Authorization: Bearer $PREDEV_API_KEY" -o video.mp4
components:
  parameters:
    ProjectId:
      name: x-predev-project-id
      in: header
      required: false
      schema:
        type: string
      description: >-
        Attribute this call to one of the workspace's projects. An id the
        workspace does not own is ignored.
  schemas:
    VideoRequest:
      type: object
      required:
        - model
      properties:
        model:
          type: string
          description: An id from `GET /v1/videos/models`.
          example: bytedance/seedance-2.0-mini
        prompt:
          type: string
          description: What to generate. Optional for models that work from images alone.
        duration:
          type: integer
          description: Seconds. See the model row's `supported_durations`.
        resolution:
          type: string
          examples:
            - 480p
            - 720p
            - 1080p
        aspect_ratio:
          type: string
          examples:
            - '16:9'
            - '9:16'
            - '1:1'
        size:
          type: string
          description: Exact pixels, such as `1280x720`.
        frame_images:
          type: array
          items:
            type: object
            additionalProperties: true
          description: >-
            First or last frame images, on models that list
            `supported_frame_images`.
        input_references:
          type: array
          items:
            oneOf:
              - type: string
                description: 'A URL or data: URL of a reference image.'
              - type: object
                additionalProperties: true
          description: >-
            Reference images, audio or video, on models that accept them. An
            image can be a URL or data: URL string.
        generate_audio:
          type: boolean
        seed:
          type: integer
      additionalProperties: true
    VideoJob:
      type: object
      required:
        - id
        - polling_url
        - status
      properties:
        id:
          type: string
          description: >-
            Job id. Poll `GET /v1/videos/{id}` and download from `GET
            /v1/videos/{id}/content`.
        polling_url:
          type: string
          format: uri
          description: >-
            `https://api.pre.dev/v1/videos/{id}` for this job: the status URL,
            ending with the job's `id`.
        status:
          type: string
          enum:
            - pending
            - in_progress
            - completed
            - failed
            - cancelled
            - expired
        generation_id:
          type: string
        unsigned_urls:
          type: array
          items:
            type: string
          description: >-
            Present when the job has completed. Download through `GET
            /v1/videos/{jobId}/content`.
        error:
          type: string
          description: Why the job failed.
      additionalProperties: true
    InvalidJsonBody:
      type: object
      description: The request body is not valid JSON. Checked before authentication.
      required:
        - error
      properties:
        error:
          type: string
          const: Invalid JSON body
    ModelError:
      type: object
      description: >-
        An error returned by the model for this request. The HTTP status is the
        model's own; `code` usually repeats it.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - message
          properties:
            message:
              type: string
            code:
              type:
                - integer
                - string
            metadata:
              type: object
              properties:
                raw:
                  type: string
                  description: The model's own error text, when it adds detail.
                reasons:
                  type: array
                  items:
                    type: string
                  description: Moderation categories when the input was flagged.
                flagged_input:
                  type: string
                  description: The part of the input that was flagged.
    GatewayError:
      type: object
      description: >-
        An error raised by pre.dev, in the OpenAI error shape with an extra
        `predev` object (absent on `catalog_unavailable` from the catalog
        listings). Branch on `error.code`.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - message
            - type
            - code
            - param
          properties:
            message:
              type: string
              description: What went wrong and what to do next.
            type:
              type: string
              enum:
                - authentication_error
                - insufficient_credits
                - subscription_required
                - invalid_request_error
                - not_found_error
                - rate_limit_error
                - server_error
              description: >-
                Error class. The OpenAI and Anthropic SDKs map the HTTP status
                to their own exception types.
            code:
              type: string
              enum:
                - missing_api_key
                - invalid_api_key
                - too_many_auth_failures
                - insufficient_credits
                - subscription_required
                - rate_limit_exceeded
                - balance_unavailable
                - invalid_request
                - missing_id
                - not_found
                - not_ready
                - upstream_unavailable
                - catalog_unavailable
                - internal_error
              description: >-
                | Code | HTTP | Meaning |

                | --- | --- | --- |

                | `missing_api_key` | 401 | No `Authorization: Bearer` or
                `x-api-key` header. |

                | `invalid_api_key` | 401 | The key is unknown, or was rotated
                more than 15 minutes ago. |

                | `too_many_auth_failures` | 429 | More than 30 failed key
                attempts from this IP address in a minute. |

                | `insufficient_credits` | 402 | The balance is zero or below
                the estimate for this request. Nothing ran. |

                | `subscription_required` | 402 | A free workspace used its 5
                credits of AI calls. |

                | `rate_limit_exceeded` | 429 | Over the per-minute request
                limit for the workspace. |

                | `balance_unavailable` | 503 | The balance could not be read.
                Nothing ran. |

                | `invalid_request` | 400 | `/v1/audio/speech` is missing
                `model` or `input`, or the model does not produce audio. |

                | `missing_id` | 400 | `/v1/generation` was called without `id`.
                |

                | `not_found` | 404 | Unknown path, or a file, video job,
                response or generation that belongs to another workspace,
                including one a request refers to. |

                | `not_ready` | 404 or 502 | Generation stats are not available
                yet. |

                | `upstream_unavailable` | 502 | The model did not answer, or
                returned no audio. |

                | `catalog_unavailable` | 502 or 503 | The model catalog could
                not be read: 502 from the catalog listings, 503 from `GET
                /v1/models/public`. Retry shortly. |

                | `internal_error` | 500 | Unexpected error. |
            param:
              type: 'null'
            predev:
              $ref: '#/components/schemas/GatewayErrorDetails'
    GatewayErrorDetails:
      type: object
      description: Extra fields for some error codes; an empty object for the rest.
      properties:
        credits_remaining:
          type: number
          description: >-
            Workspace balance. Sent with `insufficient_credits` and
            `subscription_required`.
        estimated_credits:
          type: number
          description: >-
            Estimated credits the request needs. Sent with
            `insufficient_credits`.
        trial_credits_used:
          type: number
          description: >-
            Credits the free trial has spent on AI calls. Sent with
            `subscription_required`.
        topup_url:
          type: string
          format: uri
          description: Where to add credits or subscribe.
        retry_after:
          type: integer
          description: Seconds to wait. Also sent as the `Retry-After` header.
        available_models:
          type: array
          items:
            type: string
          description: >-
            Speech and music model ids. Sent with `invalid_request` from
            `/v1/audio/speech`.
  headers:
    RequestId:
      description: >-
        pre.dev id for this request (`pdr_…`). Quote it when you contact
        support.
      schema:
        type: string
        example: pdr_043293a6-d1a8-4695-81fe-603ad4661ebb
    CreditsCharged:
      description: >-
        Credits charged for this call. Streamed responses do not carry it
        because the charge settles after the stream ends; read `GET
        /v1/generation` for those.
      schema:
        type: number
        example: 0.005
    CreditsRemaining:
      description: >-
        Workspace credit balance after this call. Omitted on plans without a
        credit limit.
      schema:
        type: number
        example: 412.5
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        example: 12
  responses:
    BadRequest:
      description: >-
        The body is not valid JSON (checked before authentication), or the model
        rejected the request, for example an unknown model id. Errors from the
        model keep their status and use the `ModelError` shape.
      content:
        application/json:
          schema:
            anyOf:
              - $ref: '#/components/schemas/InvalidJsonBody'
              - $ref: '#/components/schemas/ModelError'
          examples:
            invalid_json:
              summary: Body is not valid JSON
              value:
                error: Invalid JSON body
            unknown_model:
              summary: Unknown model id
              value:
                error:
                  message: nope/not-a-model is not a valid model ID
                  code: 400
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/GatewayError'
          examples:
            missing_api_key:
              summary: No key sent
              value:
                error:
                  message: >-
                    Send your pre.dev API key as `Authorization: Bearer <key>`
                    or `x-api-key`. Copy it from Integrations → Built-in:
                    https://pre.dev/projects/integrations
                  type: authentication_error
                  code: missing_api_key
                  param: null
                  predev: {}
            invalid_api_key:
              summary: Unknown or retired key
              value:
                error:
                  message: Invalid API key
                  type: authentication_error
                  code: invalid_api_key
                  param: null
                  predev: {}
    PaymentRequired:
      description: >-
        The workspace cannot pay for this request. Nothing was sent to the model
        and nothing was charged.
      headers:
        x-predev-credits-remaining:
          $ref: '#/components/headers/CreditsRemaining'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/GatewayError'
          examples:
            no_credits:
              summary: Balance is zero
              value:
                error:
                  message: >-
                    This pre.dev workspace has no credits left for AI calls. Top
                    up at https://pre.dev/billing or enable auto-recharge.
                  type: insufficient_credits
                  code: insufficient_credits
                  param: null
                  predev:
                    credits_remaining: 0
                    estimated_credits: 0.05
                    topup_url: https://pre.dev/billing
            below_estimate:
              summary: Balance is below the estimate
              value:
                error:
                  message: >-
                    This request needs about 10.00 credits and the workspace has
                    3.20 left. Top up at https://pre.dev/billing.
                  type: insufficient_credits
                  code: insufficient_credits
                  param: null
                  predev:
                    credits_remaining: 3.2
                    estimated_credits: 10
                    topup_url: https://pre.dev/billing
            trial_used:
              summary: Free trial allowance used
              value:
                error:
                  message: >-
                    The free trial includes 5 credits of AI calls and this
                    workspace has used them. Subscribe at
                    https://pre.dev/billing to keep going.
                  type: subscription_required
                  code: subscription_required
                  param: null
                  predev:
                    credits_remaining: 14.2
                    trial_credits_used: 5.0132
                    topup_url: https://pre.dev/billing
    ModelNotFound:
      description: >-
        The request refers to a file, response or video job that is not this
        workspace's (`not_found` from pre.dev; nothing ran and nothing was
        charged), or the model could not serve the request (its own status and
        message, in the `ModelError` shape).
      content:
        application/json:
          schema:
            anyOf:
              - $ref: '#/components/schemas/GatewayError'
              - $ref: '#/components/schemas/ModelError'
          examples:
            foreign_reference:
              summary: A file that is not this workspace's
              value:
                error:
                  message: No file with id file-2Lm9Rt belongs to this workspace.
                  type: not_found_error
                  code: not_found
                  param: null
                  predev: {}
            model_not_found:
              summary: The model cannot serve the request
              value:
                error:
                  message: No endpoints found matching your data policy.
                  code: 404
    RateLimited:
      description: >-
        Too many requests. Wait `Retry-After` seconds when it is present. A
        `rate_limit_exceeded` refusal is per workspace per minute (600, or 30 on
        the free trial). An error from the model keeps the `ModelError` shape.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            anyOf:
              - $ref: '#/components/schemas/GatewayError'
              - $ref: '#/components/schemas/ModelError'
          examples:
            rate_limit_exceeded:
              summary: Workspace rate limit
              value:
                error:
                  message: >-
                    Rate limit of 600 requests per minute exceeded for this
                    workspace.
                  type: rate_limit_error
                  code: rate_limit_exceeded
                  param: null
                  predev:
                    retry_after: 12
            too_many_auth_failures:
              summary: Failed key attempts from this IP
              value:
                error:
                  message: >-
                    Too many failed API key attempts from this address. Try
                    again in a minute.
                  type: rate_limit_error
                  code: too_many_auth_failures
                  param: null
                  predev:
                    retry_after: 60
            model_rate_limited:
              summary: The model is rate-limited
              value:
                error:
                  message: The model returned an error
                  code: 429
                  metadata:
                    raw: deepseek/deepseek-v4.1-flash is temporarily rate-limited.
    ServerError:
      description: Unexpected error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/GatewayError'
          example:
            error:
              message: The pre.dev AI gateway hit an unexpected error.
              type: server_error
              code: internal_error
              param: null
              predev: {}
    ModelUnavailable:
      description: >-
        The model did not answer. Retry with backoff, or send `models`
        fallbacks.
      content:
        application/json:
          schema:
            anyOf:
              - $ref: '#/components/schemas/GatewayError'
              - $ref: '#/components/schemas/ModelError'
          examples:
            upstream_unavailable:
              summary: No answer from the model
              value:
                error:
                  message: 'The model provider did not answer: timed out'
                  type: server_error
                  code: upstream_unavailable
                  param: null
                  predev: {}
            model_error:
              summary: The model returned a non-JSON error
              value:
                error:
                  message: The model provider returned an error (HTTP 502)
                  code: 502
    BalanceUnavailable:
      description: >-
        The credit balance could not be read, so nothing ran. Retry after
        `Retry-After` seconds.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/GatewayError'
          example:
            error:
              message: Could not read this workspace's credit balance. Retry shortly.
              type: server_error
              code: balance_unavailable
              param: null
              predev:
                retry_after: 5
  securitySchemes:
    apiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: pdk_ API key
      description: >-
        The workspace API key (`pdk_…`) from Integrations → Built-in in the
        pre.dev dashboard.
    xApiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Alternative to `Authorization: Bearer`. The Anthropic SDKs send this
        header.

````