# Seedance 2.5 Lock a face, a wardrobe, a world in place — up to 50 image, video and audio references. - Model ID: `bytedance/seedance-2.5/reference-to-video` - Provider: ByteDance - Modality: image → video - Type: Reference to Video - Status: stable - Delivery: async - Max resolution: 1080p - Output format: mp4 - Aspect ratios: 1:1, 4:3, 3:4, 16:9, 9:16, 21:9, adaptive ## Authentication Send your API key in the `X-API-Key` header. The same key reads the org balance at `GET https://api.dev.pika.art/billing/balance`, which is free to call and confirms the request can be paid for before you submit it. ## Endpoint ``` POST https://api.dev.pika.art/v1/media/bytedance/seedance-2.5/reference-to-video ``` ## Reference uploads To use a local file (image, audio, or video) as a model input, first request a presigned upload URL: `POST /v1/media/uploads` with the `X-API-Key` header and a JSON body `{ "content_type": "image/png", "size_bytes": 12345 }`, where `size_bytes` is the file's exact byte length. The response returns `upload_url` (temporary, valid for 5 minutes), `headers`, and `url` (the permanent Pika URL). PUT the file bytes to `upload_url` sending the returned `headers` verbatim — both `Content-Type` and `Content-Length` are signed, and storage returns a bare 403 if either is missing or differs. Then pass `url` into fields such as `image`, `image_url`, `end_image_url`, `image_urls`, `first_frame_image`, or `mask_image_url`. ## Generate `POST /v1/media/bytedance/seedance-2.5/reference-to-video` ```bash curl -X POST https://api.dev.pika.art/v1/media/bytedance/seedance-2.5/reference-to-video \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "@Image1 walking through @Video1's scene, cinematic lighting.", "duration": 10, "resolution": "720p", "generate_audio": true, "watermark": false, "ratio": "16:9", "image_urls": [ "https://cdn.pika.art/v2/files/agent/754c2b3b-b543-4a15-b049-3da18f56c4db/seedance-2.5-r2v-ref-petrol-pump-flacon.jpg", "https://cdn.pika.art/v2/files/agent/0fd18699-d9d7-4ebf-a051-d73c9d87f724/seedance-2.5-r2v-ref-ear.jpg" ], "video_urls": [ "https://example.com/reference.mp4" ] }' ``` ### Request body | Param | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `prompt` | string | yes | - | Text prompt describing the video to generate. | | `duration` | enum | yes | - | Video length in seconds (4-30), or 'auto' to let the model choose. | | `resolution` | enum | no | 720p | Output resolution (480p/720p/1080p for 2.5). | | `generate_audio` | boolean | no | true | Whether to generate audio for the video. | | `watermark` | boolean | no | false | Whether to add a watermark to the output video. | | `output_format` | enum | no | - | Output container format. | | `bitrate_mode` | enum | no | - | Output encode bitrate. 'high' produces a larger, higher-bitrate file; defaults to 'standard'. | | `ratio` | enum | no | - | Output aspect ratio. | | `image_urls` | string[] | no | - | Up to 30 reference images (referenced in the prompt as @Image1...). | | `video_urls` | string[] | no | - | Up to 10 reference videos (referenced in the prompt as @Video1...). Each must be mp4 or mov, 2-30 seconds long, at most 200 MB, 24-60 fps; the reference videos total at most 30 seconds. Editing or extending a video is requested through the prompt (e.g. 'edit @Video1: ...', 'extend @Video1 ...'); editing requires duration 'auto' and keeps the source ratio and length. | | `audio_urls` | string[] | no | - | Up to 10 reference audio clips (referenced in the prompt as @Audio1...). Each must be wav or mp3, 2-30 seconds long, at most 15 MB; the clips total at most 30 seconds. | ### Accepted values | Param | Values | Default | | --- | --- | --- | | `resolution` | `480p`, `720p`, `1080p` | `720p` | | `output_format` | `mp4`, `mov` | — | | `bitrate_mode` | `standard`, `high` | — | | `ratio` | `1:1`, `4:3`, `3:4`, `16:9`, `9:16`, `21:9`, `adaptive` | — | ### Referencing inputs in the prompt Reference inputs are attached as flat URL arrays and cited positionally inside the `prompt` text: - `image_urls[]` (up to 9) → cite as `@Image1`, `@Image2`, … - `video_urls[]` (up to 3) → cite as `@Video1`, `@Video2`, … - `audio_urls[]` (up to 3) → cite as `@Audio1`, `@Audio2`, … At most 12 files combined across the three arrays. If `audio_urls` is set, include at least one image or video. Example: `prompt` = `@Image1 walking through @Video1's scene` with the matching URLs in `image_urls` and `video_urls`. Returns a job object, not the final output — normally with status `queued`. Store the `id` and poll until a terminal state. A rejected submit (insufficient balance, rate limit, unpriceable input) still returns the job object, with status `failed` and `error` set. An `Idempotency-Key` replay returns the existing job in its current state — including `failed`, so retry a failure with a fresh key, never the same one. ### Response **200 OK · queued** ```json { "id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0", "status": "queued" } ``` ## Poll status `GET /v1/media/jobs/{request_id}` ```bash curl https://api.dev.pika.art/v1/media/jobs/{request_id} \ -H "X-API-Key: YOUR_API_KEY" ``` Poll the job by id until it reaches a terminal state: `completed` or `failed`. ### The job object - `id` (string): Unique identifier for the job. - `status` (enum): One of `queued`, `running`, `completed`, `failed`. - `output` (object): Present once the job completes. - `media_type` (enum): `video`. - `output.video.url` (string): URL of the generated video. - `error` (object): Present if the job failed. `error.code` is the stable machine-readable value to branch on — one of `invalid_input`, `content_moderation`, `provider_error`, `provider_timeout`, `provider_unavailable`, `rate_limited`, `insufficient_balance`, `membership_required`, `cycle_limit_exceeded`, `admission_suspended`, `timed_out`, `internal`. Unrecognized values normalize to `provider_error`. `error.message` is diagnostic text and varies. ### Response **200 OK · running** ```json { "id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0", "status": "running" } ``` **200 OK · completed** ```json { "id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0", "status": "completed", "output": { "media_type": "video", "video": { "url": "https://api.dev.pika.art/v1/files/video_8f3a2c91.mp4", "content_type": "video/mp4" } } } ``` **200 OK · failed** ```json { "id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0", "status": "failed", "error": { "code": "provider_error", "message": "upstream generation failed" } } ``` ## Get result `GET /v1/media/jobs/{request_id}/content` ```bash curl https://api.dev.pika.art/v1/media/jobs/{request_id}/content \ -H "X-API-Key: YOUR_API_KEY" ``` Once the job completes, fetch a download URL for the generated media. ### Response **200 OK · content URL** ```json { "url": "https://api.dev.pika.art/v1/files/video_8f3a2c91.mp4" } ``` ## Errors Errors use conventional HTTP status codes and always return JSON, in one of two shapes. Request-level errors (auth, unknown path, schema validation, idempotency conflict) are `{"message": "..."}`. Submit rejections that occur after the job row is created (insufficient balance, rate limit, unpriceable input, dispatch unavailable) return the full failed job envelope — branch on `error.code`, and retry with a fresh `Idempotency-Key`, since a failed job replays on its key. - 401 Unauthorized — The API key is missing or invalid. Body: `{"message":"Invalid API key"}` - 403 Forbidden — An inactive key is rejected with a `{"message"}` body. A submit that the org balance or postpaid cycle limit cannot cover is rejected after the job row exists and returns the failed job envelope. `GET /billing/balance` separates the two: a `200` means the key is active, so the rejection was the balance or the cycle limit. Body: `{"id":"media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0","status":"failed","error":{"code":"insufficient_balance","message":"Insufficient org balance"}}` - 404 Not Found — No job with that id exists in your org, or the media path names an unknown vendor/model/function. Body: `{"message":"media job not found"}` - 409 Conflict — The result was requested before the job completed, or an Idempotency-Key header was reused with a different body. Body: `{"message":"media job is not ready"}` - 422 Unprocessable Entity — The request body failed validation: an invalid enum value, a missing required field, a wrong type, or malformed JSON. The JSON message names the offending field. A schema-valid parameter combination that cannot be priced instead fails after job creation and returns the failed job envelope with error code `invalid_input`. Body: `{"message":"duration: Input should be less than or equal to 15"}` - 429 Too Many Requests — Org limit reached: requests per minute or day, or concurrent jobs. Returned as the failed job envelope; check the Retry-After header and retry with a fresh Idempotency-Key. Body: `{"id":"media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0","status":"failed","error":{"code":"rate_limited","message":"rate limit exceeded: rpm"}}` - 503 Service Unavailable — The model rail or a backend dependency is temporarily unavailable. Returned as the failed job envelope; retry with backoff and a fresh Idempotency-Key. Body: `{"id":"media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0","status":"failed","error":{"code":"provider_unavailable","message":"media dispatch unavailable"}}` ## Pricing Video output is metered in tokens, and the token count scales with pixel count — a lower per-token rate at a higher resolution is **not** a lower price. Compare the derived per-second cost, not the rate per 1M tokens. | Tier | $ / 1M tokens | Video tokens / sec | $ / sec | $ / 5s clip | | --- | --- | --- | --- | --- | | 480p · standard · no video input | 11.235 | 10,000 | 0.1124 | 0.56 | | 480p · standard · with video input | 6.72 | 20,000 | 0.1344 | 0.67 | | 720p · standard · no video input | 11.235 | 21,600 | 0.2427 | 1.21 | | 720p · standard · with video input | 6.72 | 43,200 | 0.2903 | 1.45 | | 1080p · standard · no video input | 9.36 | 48,600 | 0.4549 | 2.27 | | 1080p · standard · with video input | 5.6 | 97,200 | 0.5443 | 2.72 | The derived columns use each resolution's nominal token rate; billing settles on the token count the model actually reports. Only successful generations are charged. Check the balance against this price before submitting: `GET https://api.dev.pika.art/billing/balance` returns `balance_micro_usd`, which is US dollars multiplied by 1,000,000, and `postpaid` when the org bills by invoice instead. For an active postpaid org, compare the price against `postpaid.cycle.remaining_micro_usd` rather than the prepaid balance. Report a shortfall to the user rather than submitting a request the balance cannot cover. --- Model page: https://dev.pika.art/models/bytedance/seedance-2.5/reference-to-video API index: https://dev.pika.art/llms.txt