---
title: "Create a video"
method: POST
path: "/videos"
tags: ["Video"]
---

# Create a video

`POST /videos`

## Request

- Base URL: `https://api.propertypixel.app/v1`
- URL: `https://api.propertypixel.app/v1/videos`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://llflgtovnkrsjqydycyu.supabase.co/auth/v1/oauth/authorize`, token URL `https://llflgtovnkrsjqydycyu.supabase.co/auth/v1/oauth/token`)

Turn listing photos into a video. One photo makes a Photo to Video clip; 2–9 photos make a Listing Tour, in the order given. Costs 400 credits per photo.

Defaults: clip_seconds 3, aspect_ratio "auto"; each photo uses its original unless versions names an enhanced result.

Spends credits in two steps. Call without quote_id first: you get a quote (cost, balance before and after, whether the balance covers it, and whether results will be watermarked) and nothing is spent. Show the cost to the user and wait for a yes, then call again with the same arguments plus quote_id. The quote lasts 10 minutes and only matches identical arguments.

Results are asynchronous: this returns job IDs right away (status pending). Poll get_job_status with the job IDs, waiting poll_after_ms between calls, until each job is completed or failed. Failed jobs are refunded automatically. Videos take a few minutes.

Requires a connection with edit access. Spends credits. Without `quote_id` it returns 409 `quote_required` with a quote and spends nothing. Resend with `quote_id` to run, or send `auto_confirm: true` to skip the quote.

## Headers

- `Idempotency-Key` string: A unique key (for example a UUID) that makes retries safe. A retry with the same key and body returns the stored response; the same key with a different body returns 422. Keys last 24 hours.

## Request body

- object
  - `project_id` string, uuid, required: The project the photos belong to.
  - `photo_ids` string[], required: Photos in playback order, 1–9. One photo makes a short clip; several are joined into a Listing Tour with crossfades.
  - `clip_seconds` integer
  - `aspect_ratio` 'auto' | '16:9' | '9:16' | '1:1'
  - `versions` object: Edit on top of a previous result instead of the original: map of photo_id → job_id of a completed job on that photo. Photos not listed use the original.
  - `quote_id` string, uuid: The id of a quote the user confirmed. Omit it to get a quote first; nothing is spent without one.
  - `auto_confirm` boolean: REST with a personal API key only. Skip the quote step and spend immediately. For trusted scripts; ignored for app (OAuth) connections, which always get a quote.

## Response `201`

Create a video

- JobBatch
  - `object` 'job_batch', required
  - `jobs` Job[], required
    - `object` 'job', required
    - `id` string, uuid, required
    - `kind` 'enhancement' | 'staging' | 'video', required
    - `status` 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled', required
    - `progress` number, nullable, required: Percent complete while processing.
    - `stage` string, nullable, required
    - `project_id` string, uuid, nullable, required
    - `photo_id` string, uuid, nullable, required: Source photo (null for multi-photo videos).
    - `photo_ids` string[], required: All source photos, in order (videos can have up to 9).
    - `source_job_id` string, uuid, nullable, required: Prior version this edit was applied on top of.
    - `enhancement_types` string[], required
    - `design_style` string, nullable, required
    - `ultra_hd` boolean, required
    - `credits` integer, nullable, required
    - `picked` boolean, required
    - `watermarked` boolean, required
    - `result` MediaUrl, required
      - `url` string, uri, required
      - `expires_at` string, date-time, required: Signed URL expiry (about 1 hour). Re-fetch for a fresh URL.
    - `thumbnail` MediaUrl, required
      - `url` string, uri, required
      - `expires_at` string, date-time, required: Signed URL expiry (about 1 hour). Re-fetch for a fresh URL.
    - `error` object, nullable, required: Why the job failed. Failed jobs are refunded automatically.
      - `code` string, required
      - `message` string, required
    - `created_at` string, date-time, required
    - `completed_at` string, date-time, nullable, required
  - `failed` object[], required
    - `photo_id` string, required
    - `error` string, required
  - `credits_spent` integer, required
  - `poll_after_ms` integer, required: Suggested wait before calling get_job_status.

## Other responses

- `400`: The request is invalid. Codes: `validation_failed`.
- `401`: Missing, invalid or revoked credential. Codes: `unauthorized`.
- `402`: The workspace does not have enough credits. Codes: `insufficient_credits`.
- `403`: The credential can't do this. View-only connections get forbidden_scope on writes. Codes: `forbidden_scope`, `forbidden`.
- `404`: The resource doesn't exist in this workspace. Codes: `not_found`.
- `409`: This request spends credits and has no valid quote. Codes: `quote_required` (the body's `quote` holds the price), `quote_expired`, `quote_mismatch`, `idempotency_in_progress`, `conflict`.
- `422`: An Idempotency-Key was reused with a different body, or an upload was rejected. Codes: `idempotency_conflict`, `upload_rejected`.
- `429`: Too many requests for this connection. Codes: `rate_limited`.
- `500`: Something failed on our side. Retry later. Codes: `internal`.

---

[API](https://skmtc.dev/propertypixel/apis/propertypixel-api.md) · [All operations](https://skmtc.dev/propertypixel/apis/propertypixel-api/llms.txt) · [OpenAPI document](https://skmtc.dev/propertypixel/apis/propertypixel-api/revisions/8f7baef701cc?raw)
