Pika H3 Flex
Animate a first frame, last frame or both into a video with synchronized audio. Choose any whole-second duration from 5 to 15, 480p or 768p resolution, and one of 11 aspect ratios. Optionally rewrite the prompt using the images. 1080p/2k/4k generates at 768p and upscales once with Pika Upsampler V1, preserving audio and frame rate. Combined pricing is per actual output second; the quote reserves the native frame-count duration bound.
Capabilities
Best for
- •Cinematic text-to-video shots
- •Short social and ad clips
- •Consistent multi-shot sequences
- •Look-dev and style exploration
Not for
- •Frame-exact rotoscoping
- •Feature-length renders
- •Pixel-perfect compositing
- •Precise on-screen text
Authentication
api key header
The Pika API uses API keys to authenticate requests. Send your key in the X-API-Key header. Your API key is a secret. Don't expose it in browsers or other client-side code. Instead, call the API from your server. The signed-in playground uses a portal session and never sends your API key.
X-API-Key: YOUR_API_KEY
Endpoint
asynchronous · poll for the result
Reference Uploads
media inputs
Media inputs accept any public URL. To use a local file, POST its content_type and size_bytes to /v1/media/uploads, PUT the file to the returned upload_url, then pass the url.
# 1. Request a presigned upload URL. Send the file's content# type and its exact size in bytes.curl -X POST https://api.dev.pika.art/v1/media/uploads \-H "X-API-Key: YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"content_type": "image/png", "size_bytes": 12345}'# The response returns two URLs:# upload_url: a temporary URL to upload the file to. Expires in 5 minutes.# url: the permanent Pika URL. Pass it to the model as the input.# {# "upload_url": "https://upload.r2.pika.art/...?X-Amz-Signature=...",# "url": "https://cdn.pika.art/v2/media/uploads/org_abc/9f8e7d.png"# }# 2. Upload the file to upload_url, then pass url to the model.curl -X PUT "<upload_url>" \-H "Content-Type: image/png" \--data-binary @input.png
Generate
POST /v1/media/pika/pika-h3-flex/image-to-video
Request
curl -X POST https://api.dev.pika.art/v1/media/pika/pika-h3-flex/image-to-video \-H "X-API-Key: YOUR_API_KEY" \-H "Content-Type: application/json" \-d '{"prompt": "Start exactly on the first frame: three people in matching pale-yellow velour tracksuits with white stripes — a man on the left holding one end of a tape measure, a woman at the back holding the other end, and a curly-haired man kneeling at the front right — lining up rows of blue folding chairs in a wood-panelled hall. Soft daylight, muted 1970s film tones, fine grain, deadpan comedy.\n[Shot 1, 00:00–00:04] The same high angle, the camera slowly craning down: the man on the left reads the tape measure and says flatly: \"Forty-two centimetres.\" The woman at the back answers, without looking up: \"It has to be forty.\"\n[Shot 2, at 00:04 cut] Close-up on the floor: the kneeling man's knuckle nudges a chair leg exactly two centimetres to the left; the tape measure snaps into place beside it; he nods once.\n[Shot 3, at 00:08 cut] Directly overhead, top-down: the camera rotates slowly above the now perfect grid of blue chairs; the three of them step back into the corners of the frame at the same time.\nKeep every person, animal, object, outfit and the location exactly as in the first frame; nobody transforms or morphs into anyone else. Real people speak with natural lip-sync. SPEED IS CRITICAL: everything moves at normal real-time speed, like 24fps footage played back at 24fps - natural motion, NO slow motion.\nSound: the zip of a tape measure, metal chair legs scraping on parquet, an echoey empty hall, footsteps, a creaking door, a long awkward silence. Music: a dry, plinky bassoon-and-pizzicato comedy cue.","duration": 15,"resolution": "768p","seed": 0,"rewrite": true,"output_mode": "video","channel_mode": "stereo","image": "https://msaocnoosm1a4.pika.art/v2/media/uploads/b20e8900-95df-4681-9f1b-a6b5c5abc236/9d460296eb6b4e1e8c1154a43959e10c.png"}'
Response
{"id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0","status": "queued"}
Request body
Accepted values
- aspect_ratio
- resolution
- output_mode
- channel_mode
Input modes
Use a public URL directly, or POST { content_type, size_bytes } to /v1/media/uploads, PUT the image to the returned upload_url with the returned headers, then pass the url.
Use a public URL directly, or POST { content_type, size_bytes } to /v1/media/uploads, PUT the image to the returned upload_url with the returned headers, then pass the url.
Returns
Returns a job object with status , not the final output. Store the id from the response and poll the job until it completes.
Poll status
GET /v1/media/jobs/{request_id}
Request
curl https://api.dev.pika.art/v1/media/jobs/{request_id} \-H "X-API-Key: YOUR_API_KEY"
Response
{"id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0","status": "running"}
Poll the job by id until it reaches a terminal state: completed or failed. A completed job carries its download URL in output, and GET /v1/media/jobs/{request_id}/content returns that same URL as { "url" } for clients that only kept the id.
DELETE /v1/media/jobs/{request_id} erases the stored media for a job, and the request captured at submit (the prompt and parameters). The job then reports deleted_at and no output, and the content endpoint answers 410.
The job object
Errors
shared across calls
Errors use conventional HTTP status codes with a JSON body of the shape {"message": "..."}. Validation failures return 422 with a message naming the offending field. Submit rejections after the job row exists (balance, rate limit) return the failed job envelope — branch on error.code.
| Status | When | Body |
|---|---|---|
| 401 Unauthorized | The API key is missing or invalid. | {"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. | {"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. | {"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. | {"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. | {"message":"duration: Input should be less than or equal to 15"} |
| 429 Too Many Requests | You've hit your per-minute or per-day request limit, or your org's job queue is full. Returned as the failed job envelope. If there's a Retry-After header, wait that long, then retry with a fresh Idempotency-Key. | {"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. | {"id":"media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0","status":"failed","error":{"code":"provider_unavailable","message":"media dispatch unavailable"}} |