---
title: "Photo to Video"
method: POST
path: "/api/v1/tools/photo_to_video"
tags: ["Tools"]
---

# Photo to Video

`POST /api/v1/tools/photo_to_video`

## Request

- Base URL: `https://pixly.app`
- URL: `https://pixly.app/api/v1/tools/photo_to_video`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://pixly.app/oauth/authorize`, token URL `https://pixly.app/api/oauth/token`, scopes: `tools`)

Turn a start photo — and optionally an end photo — into a video. With an end photo the camera travels from the first photo and lands exactly on the second (e.g. two views of the same kitchen → an orbit around the island). Pick a ready-made camera move or write your own prompt; choose the model, length and sound.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `imageUrl` string: Start frame — where the video begins. Public https URL or data: URI; either imageUrl or r2Path is required.
  - `r2Path` string: Storage path of a photo already in the user's Pixly library (from list_library, or returned by an upload ticket) — the alternative to imageUrl.
  - `endImageUrl` string: Optional end frame — the camera travels from the start photo and lands exactly on this one. Best when both photos show the same space from different spots.
  - `endR2Path` string: R2 path of the end frame — the alternative to endImageUrl.
  - `cameraMove` 'smooth' | 'walk' | 'orbitLeft' | 'orbitRight' | 'pushIn' | 'pullBack' | 'riseUp': Ready-made camera move used when no prompt is given.
  - `prompt` string: Your own description of the video, in any language. Replaces cameraMove when set.
  - `model` 'kling-v3' | 'kling-2.6': kling-v3: 3/5/8/10/15 s, optional sound, lands most precisely on the end frame. kling-2.6: 5/10 s, silent, cheaper.
  - `durationSeconds` integer: Clip length. kling-v3 takes 3, 5, 8, 10 or 15; kling-2.6 takes 5 or 10.
  - `sound` boolean: Add ambient sound (kling-v3 only; kling-2.6 is always silent).
  - `format` 'original' | '16:9' | '9:16' | '1:1': Video shape. original (default) keeps the start photo's own shape; 16:9, 9:16 or 1:1 crop the photos, centred, to that shape before they are animated.

## Response `200`

Finished inline — resultUrls present (photo tools)

- Job
  - `jobId` string, required: Job id — pass to get_job to poll
  - `type` 'image_staging' | 'video_pair' | 'video_tour', required: Job family
  - `status` 'pending' | 'processing' | 'completed' | 'failed', required: Anything not completed/failed is still running
  - `creditsCharged` number, nullable, required: Credits debited for this job
  - `createdAt` string, date-time, required: Submission time
  - `completedAt` string, date-time: Present once finished
  - `resultUrls` string[]: Presigned download URLs (present when completed; valid 7 days)
  - `resultPaths` string[]: Storage path of each result, in resultUrls order — pass one as r2Path (or beforeR2Path, a photos[] item…) to run another tool on this result
  - `error` string: Failure reason; the message says whether the credit came back immediately or is in 24h refund review
  - `note` string: Hint for agents, e.g. poll again in a few seconds

## Other responses

- `202`: Accepted and running — poll GET /api/v1/jobs/{id} (video tools)
- `401`: Missing/invalid credential
- `402`: Out of credits, or no active subscription (error.required = credits needed) — POST /api/v1/checkout returns a payment link
- `409`: A request with the same Idempotency-Key is still running — retry in a few seconds for its result
- `410`: Source photo no longer available — re-upload
- `422`: Invalid input — error.issues lists the offending fields
- `429`: Rate limited — see Retry-After (120 req/min, 30/min for generations)

---

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