Skip to content

Videos

POST /v1/videos generates a video. The endpoint needs an API key sent as Authorization: Bearer. See Authentication. The body is JSON.

A video takes time to generate, so the endpoint never waits for the result. It always returns 202 with a job. You collect the result by polling the job or by receiving a webhook event. This also applies when you send a request again with the same Idempotency-Key, even if the job has already finished.

Send the model and a prompt. The example uses sakuranbo/cherry-1.0-pro.

Terminal window
curl -X POST "https://api.sakuranbo.ai/v1/videos" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $SAKURANBO_API_KEY" \
-d '{
"model": "sakuranbo/cherry-1.0-pro",
"prompt": "Aerial shot gliding over a misty mountain lake at sunrise, gentle wind and birdsong",
"duration": 5,
"resolution": "1080p",
"aspect_ratio": "16:9"
}'

These are the input parameters of the example model:

NameTypeRequiredDefaultConstraintsDescription
promptstringYesUp to 20,000 charactersDescription of the scene, motion, and audio.
first_frameurlNoImage to use as the first frame (image-to-video).
last_frameurlNoImage to use as the last frame. Requires first_frame.
reference_imagesurl[]NoUp to 10 itemsUp to 10 reference images. Cannot be combined with keyframes.
durationintegerNo52 to 30Video length in seconds. Billed per second.
resolutionenumNo1080pOne of: 1080p, 2k, 4kOutput resolution. Determines the per-second price.
aspect_ratioenumNoadaptiveOne of: adaptive, 16:9, 9:16, 1:1, 4:3, 3:4Aspect ratio of the output video.
audiobooleanNotrueGenerate an audio track. The price is the same with or without audio.

The request body has these fields:

NameTypeRequiredDefaultConstraints
modelstringYes1 to 128 characters
promptstringYes
first_framestringNo
last_framestringNo
reference_imagesstring[]No
durationnumberNo
resolutionstringNo
aspect_ratiostringNo
audiobooleanNo

A parameter that is not in the table, or a value outside its range, returns 400 with invalid_request. error.param names the parameter, and error.details.allowed, error.details.minimum, or error.details.maximum give the valid values.

The fields you send select how the video is made:

  • Send only prompt to generate a video from text.
  • Send first_frame to start the video from an image. You can also send last_frame to end it on an image. last_frame needs first_frame.
  • Send reference_images to generate a video that uses the images as references. The order matters, because the prompt can refer to the first image or the second image. You cannot combine reference_images with first_frame or last_frame.

An image is a public https URL or a data:image/...;base64, URI of a PNG, JPEG, WebP, or GIF image. A data URI has a smaller size limit than a URL, so send a large image as a public URL. The server fetches each URL to check it before it creates the job. It does not follow redirects, and it rejects URLs that have credentials, IP addresses, or internal host names. An image that cannot be used returns 400 with invalid_request. error.param names the field. For a URL, error.details.reason says what is wrong. For a data URI that is too large, error.details.max_bytes is the limit.

The 202 response is the job with two extra fields, polling_url and eta_seconds. Its object is video.

NameType
idstring
objectstring
modelstring
statusstring
progressinteger | null
created_atstring
completed_atstring | null
expires_atstring | null
errorobject | null
outputsobject[]
usageobject | null
costobject | null
polling_urlstring
eta_secondsinteger | null

progress and eta_seconds are estimates based on the elapsed time. Do not use them as a deadline or as a basis for billing. The Location header and polling_url both point to the video.

Poll GET /v1/videos/{id} every few seconds until status is completed, failed, cancelled, or expired. The polling_url is the URL of this request. A failed job still returns 200, and the reason is in error. See Jobs for the statuses.

Terminal window
curl "https://api.sakuranbo.ai/v1/videos/job_0123456789abcdef0123456789abcdef" \
-H "Authorization: Bearer $SAKURANBO_API_KEY"

To avoid polling, register a webhook endpoint and receive job.succeeded, job.failed, and job.cancelled events. See Webhooks.

A job that does not finish within its time limit becomes expired. It is not billed.

A busy model provider does not appear as an HTTP status of POST /v1/videos. It appears later in the error field of the job, with the code rate_limited or concurrency_limited. Create the video again after a short wait.

The model provider can also reject the prompt or a reference image because of its content. This does not appear as an HTTP status either. The job fails with the code content_policy_violation, and nothing is charged.

A completed job has one item in outputs. Its url is a signed URL that needs no API key and works until expires_at, which is 7 days after the job completes. Anyone who has the URL can download the file until then, so treat the URL as a secret. After expires_at the file is deleted, so save the videos you need.

You can also call GET /v1/videos/{id}/content with your API key. It answers with a redirect to the signed URL, so your client must follow redirects. If the job has no file, it returns 404 and error.details.status is the status of the job. This happens when the job is still running, has failed, was cancelled, or expired, and when the file has already been deleted.

GET /v1/videos lists your videos, newest first, with the same limit, cursor, and status parameters as the job list. POST /v1/videos/{id}/cancel cancels a video that is queued or running. Both follow the rules of the job endpoints. See Jobs.

Send an Idempotency-Key header with a value you choose. If a request with the same key and the same content arrives again, you get the same job and you are charged once. The same key with a different request returns 409 with idempotency_conflict. The key is remembered for a limited time, which is stated in the API reference.

When a request is accepted, Sakuranbo AI reserves the estimated price from your balance. The estimate comes from the duration and the resolution. When the job completes, you are charged for the requested duration at the per-second price of the resolution, which is the amount that was reserved. A job that is cancelled or expires is not billed. A failed job is normally not billed either, and its reservation returns to your balance.

In rare cases, Sakuranbo AI cannot confirm whether the request reached the model provider. The job then fails with upstream_error, and its reserved amount stays reserved until Sakuranbo AI confirms whether the request is billed. If it stays reserved, contact support with the job ID.

For prices, see the model catalog and the pricing page, or calculate a price before you run the request with POST /v1/estimate. See the Estimate API.

CodeHTTP statusTypeDescription
invalid_request400invalid_request_errorThe request is malformed or a value is out of range. details.allowed lists the valid values when they are known.
unauthorized401authentication_errorThe API key is missing, malformed, unknown, revoked, or expired. The response does not say which.
balance_insufficient402insufficient_balanceThe available balance is lower than the amount that must be reserved for this request. error.details.limit_source is balance.
key_limit_exceeded402insufficient_balanceThis request would take the API key over its spending limit. error.details.limit_source is key, and error.details.resets_at says when the counter starts again.
account_suspended403permission_errorThe account is suspended, so image, video, and chat requests are refused. Contact support. The balance, deposits, API keys, and jobs stay available.
model_not_found404not_found_errorThe model ID does not exist in the catalog. The chat endpoints also return it for a model that is not a text model.
idempotency_conflict409conflict_errorThe Idempotency-Key was already used with a different request.
payload_too_large413invalid_request_errorThe request body is larger than the allowed size. error.details.max_bytes is the limit.
upstream_unavailable503overloaded_errorThe upstream provider is temporarily out of capacity. Retry later.

For a 429 or 503 response, wait the number of seconds in the Retry-After header before you retry. A 402 response has error.details.limit_source, which is balance or key. See Balance and key limits. For the full list of codes, see Errors.