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

# Reserve an upload

> First of the three calls that upload a video. Returns an `upload_path`
to send the bytes to, and the `video_id` you will reference from an
evaluation.

The whole sequence:

1. `POST /v1/videos` with the file's content type and size. You get back
   a `video_id` and a short-lived `upload_path`.
2. `PUT` the raw bytes to `upload_path`. **Send no `Authorization`
   header on this request** -- `upload_path` is on separate storage
   infrastructure, not this API, and a key sent there is a key
   disclosed to a third party. Declare `Content-Length`; a request that
   does not is refused. The grant expires after 15 minutes, and a
   replacement means starting again at step 1.
3. `POST /v1/videos/{video_id}/complete`, which hands the file to
   validation. Poll `GET /v1/videos/{video_id}` until `status` is
   `ready`; only then may an evaluation reference it.

Pass `content_hash` (SHA-256 of the bytes, hex) to skip the transfer
when we already hold that exact content: the response then carries
`skip_upload: true` and no `upload_path`, and the video is immediately
usable. Nothing is charged for the bytes you did not send.

This consumes an `upload` quota slot. A rejected content type or an
oversized `size_bytes` is refused before the slot is taken.




## OpenAPI

````yaml /openapi/galileo-v1.yaml post /v1/videos
openapi: 3.1.0
info:
  title: Galileo API
  version: 0.1.0
  summary: Evaluate generated videos with Galileo.
  description: |
    Submit generated videos for visual-glitch and prompt-misalignment analysis.
    Evaluation jobs run asynchronously. Retrieve a job until its status reaches
    `completed`, `partial`, or `failed`.
  contact:
    name: Physion Labs support
    email: support@physionlabs.ai
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
  - url: https://api.physionlabs.ai
    description: Production
  - url: https://api-dev.physionlabs.ai
    description: Development
security:
  - bearerAuth: []
tags:
  - name: Platform
    description: Service health and account metadata.
  - name: Evaluations
    description: Submit videos and retrieve Galileo results.
  - name: Videos
    description: Upload a video once and reference it from evaluations.
paths:
  /v1/videos:
    post:
      tags:
        - Videos
      summary: Reserve an upload
      description: |
        First of the three calls that upload a video. Returns an `upload_path`
        to send the bytes to, and the `video_id` you will reference from an
        evaluation.

        The whole sequence:

        1. `POST /v1/videos` with the file's content type and size. You get back
           a `video_id` and a short-lived `upload_path`.
        2. `PUT` the raw bytes to `upload_path`. **Send no `Authorization`
           header on this request** -- `upload_path` is on separate storage
           infrastructure, not this API, and a key sent there is a key
           disclosed to a third party. Declare `Content-Length`; a request that
           does not is refused. The grant expires after 15 minutes, and a
           replacement means starting again at step 1.
        3. `POST /v1/videos/{video_id}/complete`, which hands the file to
           validation. Poll `GET /v1/videos/{video_id}` until `status` is
           `ready`; only then may an evaluation reference it.

        Pass `content_hash` (SHA-256 of the bytes, hex) to skip the transfer
        when we already hold that exact content: the response then carries
        `skip_upload: true` and no `upload_path`, and the video is immediately
        usable. Nothing is charged for the bytes you did not send.

        This consumes an `upload` quota slot. A rejected content type or an
        oversized `size_bytes` is refused before the slot is taken.
      operationId: createVideo
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VideoCreate'
      responses:
        '201':
          description: Upload reserved, or an existing copy matched.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoReservation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  schemas:
    VideoCreate:
      type: object
      additionalProperties: false
      required:
        - content_type
        - size_bytes
      properties:
        content_type:
          type: string
          enum:
            - video/mp4
          description: Only MP4 is accepted.
        size_bytes:
          type: integer
          minimum: 1
          maximum: 52428800
          description: >-
            Exact size of the file you are about to send. It is signed into the
            grant, so the storage endpoint refuses a body that exceeds it -- an
            understated size fails the PUT rather than the reservation.
        content_hash:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: >-
            SHA-256 of the bytes, hex. Optional, and worth sending: when we
            already hold this exact content the response says `skip_upload` and
            there is nothing to transfer.
        duration_sec:
          type: number
          exclusiveMinimum: 0
          description: Optional metadata recorded on the video.
        width:
          type: integer
          minimum: 1
          description: Optional metadata recorded on the video.
        height:
          type: integer
          minimum: 1
          description: Optional metadata recorded on the video.
    VideoReservation:
      type: object
      required:
        - video_id
        - cdn_url
      properties:
        video_id:
          type: string
          description: >-
            Reference this as the evaluation's `video.upload_id`, once the video
            reaches `ready`.
        cdn_url:
          type: string
          format: uri
          description: Where the bytes will be readable once the upload completes.
        upload_path:
          type: string
          description: >-
            Where to PUT the bytes. A path or absolute URL on separate storage
            infrastructure, valid for 15 minutes, carrying its own signed grant
            -- send no `Authorization` header with it. Absent when `skip_upload`
            is true.
        skip_upload:
          type: boolean
          description: >-
            Present and true when `content_hash` matched content we already
            hold. Skip straight to referencing `video_id`: there is nothing to
            upload and no completion call to make.
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/Error'
        quota:
          $ref: '#/components/schemas/RateLimitWindow'
    Error:
      type: object
      required:
        - type
        - code
        - message
        - request_id
      properties:
        type:
          $ref: '#/components/schemas/ErrorType'
        code:
          $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
        request_id:
          type: string
    RateLimitWindow:
      type: object
      required:
        - limit
        - used
        - remaining
        - reset_at
        - reset_in_sec
        - window_sec
        - unlimited
      properties:
        limit:
          type: integer
          minimum: 0
        used:
          type: integer
          minimum: 0
        remaining:
          type: integer
          minimum: 0
        reset_at:
          type:
            - integer
            - 'null'
          minimum: 0
        reset_in_sec:
          type:
            - integer
            - 'null'
          minimum: 0
        window_sec:
          type: integer
          minimum: 1
        unlimited:
          type: boolean
    ErrorType:
      type: string
      enum:
        - invalid_request_error
        - authentication_error
        - rate_limit_error
        - api_error
    ErrorCode:
      type: string
      enum:
        - invalid_body
        - unknown_model
        - missing_video
        - missing_prompt
        - prompt_too_long
        - invalid_glitch_types
        - video_too_long
        - invalid_video
        - not_found
        - not_implemented
        - insufficient_credits
        - missing_api_key
        - invalid_api_key
        - unauthenticated
        - rate_limited
        - concurrency_limit
        - internal_error
        - model_unavailable
        - model_output_invalid
        - model_timeout
        - run_abandoned
  responses:
    BadRequest:
      description: The request cannot be processed as sent.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: The API key is missing, invalid, or revoked.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: The account reached a rate or concurrency limit.
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalServerError:
      description: The API encountered an unexpected error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  headers:
    RateLimitLimit:
      description: Maximum submissions in the current sliding window.
      schema:
        type: integer
        minimum: 0
    RateLimitRemaining:
      description: Submissions available before the next refusal.
      schema:
        type: integer
        minimum: 0
    RateLimitReset:
      description: Unix seconds when the next rate-limit slot becomes available.
      schema:
        type: integer
        minimum: 0
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        minimum: 1
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Galileo API key
      description: Use a key created in the Playground. Keys begin with `gk_`.

````