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

# Request a file upload URL

> Returns a GCS resumable upload URL and the corresponding CDN source URL. PUT your file bytes directly to `upload_url` with the correct `Content-Type` header. Once the upload completes, pass `source_url` as the `source_url` field in any render or task request.

The `upload_url` expires in **1 hour**.



## OpenAPI

````yaml /openapi.json post /v1/uploads
openapi: 3.1.0
info:
  title: Framelane API
  description: >
    Framelane renders video and runs AI media tasks from a single declarative
    request.


    ### Authentication

    Send your API key as a bearer token: `Authorization: Bearer fl_live_...`.


    ### Core workflow

    1. **Get media in.** Pass any publicly accessible URL directly, or `POST
    /v1/uploads` to
       get a signed URL, then `PUT` your file to it.
    2. **Submit one JSON payload.** `POST /v1/renders` composes a whole scene
    (canvas +
       `elements[]` + `transitions[]`) in a single request; `POST /v1/tasks/{type}` runs an AI
       operation (remove-background, gaze-redirect, super-resolution, transcribe).
    3. **Wait for completion.** Jobs are async: poll `GET
    /v1/{renders|tasks}/{id}` or register
       a webhook. Statuses end in `completed`, `failed`, or `cancelled`.
    4. **Fetch the result.** `GET /v1/{renders|tasks}/{id}/download` redirects
    to a short-lived
       signed URL for the output artifact.

    ### Discovering what's possible

    Call **`GET /v1/capabilities`** (no auth) for the machine-readable catalog
    of every effect,

    motion, transition, format, element type, and task parameter — each flagged
    with whether the

    renderer supports it, plus all numeric ranges and rate limits. Validate
    against it before

    submitting to avoid `422`s.


    ### Idempotency

    `POST` endpoints accept an `Idempotency-Key` header. The same key with the
    same body replays

    the original response (`200`); with a different body it returns `409
    Conflict`.


    ### Errors

    Every error has the shape `{"error": {"code", "message", "details"}}`. The
    machine-readable

    `code` (e.g. `source_not_found`, `quota_exceeded`, `codec_unsupported`) is
    stable — branch on

    it to self-correct.
  version: dev
servers:
  - url: https://api.framelane.io
    description: Production
security: []
tags:
  - name: capabilities
    description: Discover supported features, formats, and limits.
  - name: renders
    description: Compose and render video from a declarative timeline.
  - name: tasks
    description: >-
      Run AI media operations: background removal, gaze redirect,
      super-resolution, transcription.
  - name: uploads
    description: Get signed URLs to upload source media into Framelane storage.
  - name: webhooks
    description: Subscribe to job lifecycle events with signed delivery.
  - name: workspace
    description: Manage your workspace, usage, and assets.
  - name: api-keys
    description: Create and revoke API keys.
  - name: auth
    description: Session sync for the first-party console.
  - name: signup
    description: Create a workspace and verify email.
  - name: billing
    description: Manage your subscription and billing portal.
  - name: system
    description: Health, readiness, and version probes.
paths:
  /v1/uploads:
    post:
      tags:
        - uploads
      summary: Request a file upload URL
      description: >-
        Returns a GCS resumable upload URL and the corresponding CDN source URL.
        PUT your file bytes directly to `upload_url` with the correct
        `Content-Type` header. Once the upload completes, pass `source_url` as
        the `source_url` field in any render or task request.


        The `upload_url` expires in **1 hour**.
      operationId: create_upload
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadRequest'
        required: true
      responses:
        '201':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadOut'
        '401':
          description: Missing or invalid API key.
        '422':
          description: Unsupported content_type.
      security:
        - ApiKey: []
components:
  schemas:
    UploadRequest:
      properties:
        content_type:
          type: string
          title: Content Type
          description: MIME type of the file you will upload.
          example: video/mp4
        filename:
          anyOf:
            - type: string
            - type: 'null'
          title: Filename
          description: Optional original filename. Used only to set the file extension.
          example: my-clip.mp4
      type: object
      required:
        - content_type
      title: UploadRequest
    UploadOut:
      properties:
        upload_url:
          type: string
          title: Upload Url
          description: >-
            GCS resumable upload URL. PUT your file bytes here with the correct
            Content-Type.
          example: >-
            https://storage.googleapis.com/framelane-prod-user/uploads/ws_01J.../a1b2c3.mp4?upload_id=...
        source_url:
          type: string
          title: Source Url
          description: >-
            CDN URL for the uploaded file. Pass this as source_url in
            render/task requests.
          example: https://cdn-user.framelane.io/uploads/ws_01J.../a1b2c3.mp4
        expires_at:
          type: string
          format: date-time
          title: Expires At
          description: When the upload_url expires (1 hour from now).
      type: object
      required:
        - upload_url
        - source_url
        - expires_at
      title: UploadOut
      description: Response from POST /v1/uploads.
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer

````