PicassoIA Video uses the model ID picassoia/picassoia-video. Text and image to video with audio, clips of 1 to 20 seconds and 480p, 720p or 1080p output.
Calling the API
Send requests to https://api.picassoia.com/v1/models/picassoia/picassoia-video/predictions with a JSON body whose input matches the parameters below.
Setup
Keep your API key on your server, in an environment variable, and send it in the Authorization header of every request.
export PICASSOIA_API_KEY="pia_sk_…"Request Parameters
promptstringRequired- DefaultNone
- What the output should show. Up to 4000 characters.
imagestring- DefaultNone
- The opening frame, or the image to work from. A PNG, JPEG or WebP image, as a public https URL or as a data URL of up to 5 MB.
resolutionstring- Default
"480p" - The size of the output. Options: 480p, 720p and 1080p.
durationinteger- Default
5 - The length of the clip, in seconds. From 1 to 20. Up to 20 at 480p. Up to 10 at 720p. Up to 5 at 1080p.
aspect_ratiostring- Default
"match_input_image" / "16:9" - The shape of the output. Where match_input_image is an option, it keeps the proportions of the input image, or of the first one when there are several. Options: match_input_image, 1:1, 16:9, 9:16, 4:3, 3:4, 3:2 and 2:3. If you leave it out: match_input_image when image is sent, 16:9 when it is not.
save_audioboolean- Default
true - Keep the generated audio track.
seedinteger- DefaultNone
- Set it to reproduce a result; leave it out for a random one.
enhance_promptboolean- Default
true - Picasso IA rewrites your prompt for a better result. Your own prompt stays in the input.
Modes
The mode depends on which files, if any, you send with the prompt. Every mode uses the same endpoint.
Example Request
Text to Video
curl -s -X POST https://api.picassoia.com/v1/models/picassoia/picassoia-video/predictions \
-H "Authorization: Bearer $PICASSOIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input":{"prompt":"waves crashing on a rocky shore at night, cinematic"}}'const API = 'https://api.picassoia.com/v1'
const headers = {
Authorization: `Bearer ${process.env.PICASSOIA_API_KEY}`,
'Content-Type': 'application/json',
}
const sleep = (s) => new Promise((resolve) => setTimeout(resolve, s * 1000))
const input = {
prompt: "waves crashing on a rocky shore at night, cinematic"
}
const created = await fetch(`${API}/models/picassoia/picassoia-video/predictions`, {
method: 'POST',
headers,
body: JSON.stringify({ input }),
})
let prediction = await created.json()
if (!created.ok) throw new Error(`${prediction.code}: ${prediction.detail}`)
while (!['succeeded', 'failed', 'canceled'].includes(prediction.status)) {
await sleep(prediction.eta?.next_poll_in_seconds ?? 2)
prediction = await (await fetch(prediction.urls.get, { headers })).json()
}
if (prediction.status !== 'succeeded') throw new Error(prediction.error ?? prediction.status)
console.log(prediction.output)import os, time, requests
API = "https://api.picassoia.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PICASSOIA_API_KEY']}"}
payload = {"input": {
"prompt": "waves crashing on a rocky shore at night, cinematic",
}}
response = requests.post(f"{API}/models/picassoia/picassoia-video/predictions", json=payload, headers=HEADERS)
prediction = response.json()
if not response.ok:
raise RuntimeError(f"{prediction['code']}: {prediction['detail']}")
while prediction["status"] not in ("succeeded", "failed", "canceled"):
time.sleep((prediction.get("eta") or {}).get("next_poll_in_seconds", 2))
prediction = requests.get(prediction["urls"]["get"], headers=HEADERS).json()
if prediction["status"] != "succeeded":
raise RuntimeError(prediction["error"] or prediction["status"])
print(prediction["output"])Image to Video
curl -s -X POST https://api.picassoia.com/v1/models/picassoia/picassoia-video/predictions \
-H "Authorization: Bearer $PICASSOIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input":{"prompt":"the camera slowly pushes in as the subject turns to smile","image":"https://example.com/portrait.jpg"}}'const API = 'https://api.picassoia.com/v1'
const headers = {
Authorization: `Bearer ${process.env.PICASSOIA_API_KEY}`,
'Content-Type': 'application/json',
}
const sleep = (s) => new Promise((resolve) => setTimeout(resolve, s * 1000))
const input = {
prompt: "the camera slowly pushes in as the subject turns to smile",
image: "https://example.com/portrait.jpg"
}
const created = await fetch(`${API}/models/picassoia/picassoia-video/predictions`, {
method: 'POST',
headers,
body: JSON.stringify({ input }),
})
let prediction = await created.json()
if (!created.ok) throw new Error(`${prediction.code}: ${prediction.detail}`)
while (!['succeeded', 'failed', 'canceled'].includes(prediction.status)) {
await sleep(prediction.eta?.next_poll_in_seconds ?? 2)
prediction = await (await fetch(prediction.urls.get, { headers })).json()
}
if (prediction.status !== 'succeeded') throw new Error(prediction.error ?? prediction.status)
console.log(prediction.output)import os, time, requests
API = "https://api.picassoia.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PICASSOIA_API_KEY']}"}
payload = {"input": {
"prompt": "the camera slowly pushes in as the subject turns to smile",
"image": "https://example.com/portrait.jpg",
}}
response = requests.post(f"{API}/models/picassoia/picassoia-video/predictions", json=payload, headers=HEADERS)
prediction = response.json()
if not response.ok:
raise RuntimeError(f"{prediction['code']}: {prediction['detail']}")
while prediction["status"] not in ("succeeded", "failed", "canceled"):
time.sleep((prediction.get("eta") or {}).get("next_poll_in_seconds", 2))
prediction = requests.get(prediction["urls"]["get"], headers=HEADERS).json()
if prediction["status"] != "succeeded":
raise RuntimeError(prediction["error"] or prediction["status"])
print(prediction["output"])Authentication
Every request needs an API key in the Authorization header, as a Bearer token. Create keys in your account; the full key is shown once.
Authorization: Bearer $PICASSOIA_API_KEYQueue and Polling
Every prediction takes three steps: create it, poll it until it finishes, then read the result.
Submit
The API answers 201, usually within seconds, with the prediction in the starting state, an estimate in eta and the URL to poll in urls.get. If the prediction fails right away, for example when the safety filter refuses the input, the answer is still 201, with the prediction in the failed state and its error.
curl -s -X POST https://api.picassoia.com/v1/models/picassoia/picassoia-video/predictions \
-H "Authorization: Bearer $PICASSOIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input":{"prompt":"waves crashing on a rocky shore at night, cinematic"}}'const API = 'https://api.picassoia.com/v1'
const headers = {
Authorization: `Bearer ${process.env.PICASSOIA_API_KEY}`,
'Content-Type': 'application/json',
}
const input = {
prompt: "waves crashing on a rocky shore at night, cinematic"
}
const created = await fetch(`${API}/models/picassoia/picassoia-video/predictions`, {
method: 'POST',
headers,
body: JSON.stringify({ input }),
})
let prediction = await created.json()
if (!created.ok) throw new Error(`${prediction.code}: ${prediction.detail}`)
console.log(prediction.id, prediction.status) // api_…, startingimport os, requests
API = "https://api.picassoia.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PICASSOIA_API_KEY']}"}
payload = {"input": {
"prompt": "waves crashing on a rocky shore at night, cinematic",
}}
response = requests.post(f"{API}/models/picassoia/picassoia-video/predictions", json=payload, headers=HEADERS)
prediction = response.json()
if not response.ok:
raise RuntimeError(f"{prediction['code']}: {prediction['detail']}")
print(prediction["id"], prediction["status"]) # api_…, starting{
"id": "api_3f9c2b7e8d1a4c6f9e0b1a2c3d4e5f60",
"model": "picassoia/picassoia-video",
"input": {
"prompt": "waves crashing on a rocky shore at night, cinematic",
"resolution": "480p",
"duration": 5,
"aspect_ratio": "16:9",
"save_audio": true,
"enhance_prompt": true
},
"output": null,
"error": null,
"status": "starting",
"created_at": "2026-10-01T09:30:00.000Z",
"started_at": null,
"completed_at": null,
"metrics": {
"predict_time": null
},
"urls": {
"get": "https://api.picassoia.com/v1/predictions/api_3f9c2b7e8d1a4c6f9e0b1a2c3d4e5f60",
"cancel": "https://api.picassoia.com/v1/predictions/api_3f9c2b7e8d1a4c6f9e0b1a2c3d4e5f60/cancel"
},
"eta": {
"seconds": 60,
"next_poll_in_seconds": 30
}
}Poll
Poll urls.get again after eta.next_poll_in_seconds: at half the estimate, when the estimate is reached, then every 2 seconds.
# Repeat after eta.next_poll_in_seconds until status is
# succeeded, failed or canceled.
curl -s https://api.picassoia.com/v1/predictions/api_3f9c2b7e8d1a4c6f9e0b1a2c3d4e5f60 \
-H "Authorization: Bearer $PICASSOIA_API_KEY"const sleep = (s) => new Promise((resolve) => setTimeout(resolve, s * 1000))
while (!['succeeded', 'failed', 'canceled'].includes(prediction.status)) {
await sleep(prediction.eta?.next_poll_in_seconds ?? 2)
prediction = await (await fetch(prediction.urls.get, { headers })).json()
}import time
while prediction["status"] not in ("succeeded", "failed", "canceled"):
time.sleep((prediction.get("eta") or {}).get("next_poll_in_seconds", 2))
prediction = requests.get(prediction["urls"]["get"], headers=HEADERS).json(){
"id": "api_3f9c2b7e8d1a4c6f9e0b1a2c3d4e5f60",
"status": "processing",
"output": null,
"eta": {
"seconds": 30,
"next_poll_in_seconds": 30
}
}Result
When status is succeeded, output holds the result. A failed prediction carries its reason in error, and a canceled one was stopped before it finished. All three statuses are final.
# The last poll holds the result: "output" when "status" is succeeded,
# "error" when it is failed. With jq, just those three fields:
curl -s https://api.picassoia.com/v1/predictions/api_3f9c2b7e8d1a4c6f9e0b1a2c3d4e5f60 \
-H "Authorization: Bearer $PICASSOIA_API_KEY" | jq '.status, .output, .error'if (prediction.status !== 'succeeded') throw new Error(prediction.error ?? prediction.status)
console.log(prediction.output)if prediction["status"] != "succeeded":
raise RuntimeError(prediction["error"] or prediction["status"])
print(prediction["output"]){
"id": "api_3f9c2b7e8d1a4c6f9e0b1a2c3d4e5f60",
"status": "succeeded",
"output": "https://…/output.mp4",
"error": null,
"completed_at": "2026-10-01T09:30:54.000Z",
"metrics": {
"predict_time": 52.8
},
"eta": null
}Cancel
POST urls.cancel stops a prediction that is still queued (any model) or an image that is still generating; a video already generating answers 409 not_cancelable.
The IDs, times and URLs in these responses are examples: yours will have their own.
Output
When the prediction succeeds, output is the URL of an MP4 video.
Real Outputs
Errors and Limits
Errors are JSON problem details with a stable code, and a 422 names every field at fault.
{
"title": "Input validation failed",
"status": 422,
"code": "invalid_input",
"detail": "input.duration: At 1080p the longest clip is 5 seconds.",
"invalid_fields": [
{
"type": "invalid_value",
"field": "input.duration",
"description": "At 1080p the longest clip is 5 seconds."
}
]
}Limits
- 5 predictions queued or running at once per account
- Request body up to 10 MB; images as data URLs up to 5 MB each
- Prompt up to 4000 characters
- Unknown fields are refused with 422 invalid_input
Pricing
$0 per request with an active Infinite plan: API predictions use no credits, whatever the settings.
Other providers charge from $0.06/s for a comparable model (as of October 1, 2026).
API PricingJSON Schema
The same schema the API serves at GET /v1/models/picassoia/picassoia-video.
{
"input_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"prompt": {
"type": "string",
"minLength": 1,
"maxLength": 4000,
"description": "What happens in the video."
},
"image": {
"type": "string",
"description": "Opening frame (image-to-video): an https URL or a data URL."
},
"resolution": {
"default": "480p",
"description": "480p (up to 20 s), 720p (up to 10 s) or 1080p (up to 5 s).",
"type": "string",
"enum": [
"480p",
"720p",
"1080p"
]
},
"duration": {
"default": 5,
"description": "Length in seconds. The longest clip depends on the resolution.",
"type": "integer",
"minimum": 1,
"maximum": 20
},
"aspect_ratio": {
"description": "Default: match_input_image when there is an image, 16:9 without one.",
"type": "string",
"enum": [
"match_input_image",
"1:1",
"16:9",
"9:16",
"4:3",
"3:4",
"3:2",
"2:3"
]
},
"save_audio": {
"default": true,
"description": "Keep the generated audio track.",
"type": "boolean"
},
"seed": {
"description": "Random seed. Set it to reproduce a result.",
"type": "integer",
"minimum": -9007199254740991,
"maximum": 9007199254740991
},
"enhance_prompt": {
"default": true,
"description": "Let PicassoIA rewrite the prompt for a better video before generating it.",
"type": "boolean"
}
},
"required": [
"prompt"
],
"additionalProperties": false
},
"output_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string",
"format": "uri",
"description": "URL of the generated video (MP4)."
}
}