Video
Text-to-video and image-to-video on a domestic and an overseas channel. You create a task, poll it, and fetch the finished video from a link that works for about 24 hours.
Endpoint
| Base URL | https://api.nightferry.io |
|---|---|
| Create | POST /v1/videos |
| Poll | GET /v1/videos/{id} |
| Task detail | GET /v1/tasks/{id} |
| Result link | GET /v1/tasks/{id}/artifacts |
| Auth header | Authorization: Bearer <your video key> |
| Task id prefix | task_ |
Task ids are opaque strings. Store them as given.
Domestic and overseas channels
The overseas channel supports NSFW. The channel belongs to
the key, not to the request: a domestic key always runs on the domestic channel,
an overseas key always on the overseas one. There is no channel
field, and there is no automatic fallback from one channel to the other.
Both channels take the same model IDs and charge the same fixed USD prices. To use both, hold one key of each.
Models
| Model id | Resolution | Duration (s) | Images | Videos | Audio |
|---|---|---|---|---|---|
doubao-seedance-2-0-260128 | 480p / 720p / 1080p / 4k | 4–15 or −1 | 9 | 3 | 3 |
doubao-seedance-2-0-fast-260128 | 480p / 720p | 4–15 or −1 | 9 | 3 | 3 |
doubao-seedance-2-0-mini-260615 | 480p / 720p | 4–15 or −1 | 9 | 3 | 3 |
doubao-seedance-2-5-260628 | 480p / 720p / 1080p | 4–30 or −1 | 30 | 10 | 10 |
model is required. A resolution not listed for a model is
refused before anything is charged. -1 lets the model choose its
own length.
Create a task
Set NIGHTFERRY_KEY to your video key. The example uses Mini,
480p, 4 seconds and no audio.
curl -X POST https://api.nightferry.io/v1/videos \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $NIGHTFERRY_KEY" \
-d '{
"model": "doubao-seedance-2-0-mini-260615",
"prompt": "a cat running through long grass, cinematic",
"seconds": 4,
"metadata": {
"resolution": "480p",
"ratio": "16:9",
"generate_audio": false
}
}'
Creation returns the task before the video is ready:
{
"id": "task_8Qm2cVhK4pZr7TnW1yLs9XbE6dFa3JoU",
"object": "video",
"model": "doubao-seedance-2-0-mini-260615",
"status": "queued",
"progress": 0,
"created_at": 1790700000
}
| Field | Meaning |
|---|---|
model | Required. A model id from the table above. |
prompt | The text prompt. |
seconds | Output duration in seconds. Omitted, the charge held at creation assumes 15 seconds. |
metadata | The generation settings, passed on as given: resolution, ratio, generate_audio, watermark, seed, and content for reference media. |
Using a reference image
References are URLs inside metadata.content; uploading a file is
not supported. Each item is image_url, video_url or
audio_url, with an optional role:
first_frame, last_frame, reference_image,
reference_video or reference_audio. Use an HTTPS URL
the service can read.
curl -X POST https://api.nightferry.io/v1/videos \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $NIGHTFERRY_KEY" \
-d '{
"model": "doubao-seedance-2-0-mini-260615",
"prompt": "the cat turns and walks toward the camera",
"seconds": 4,
"metadata": {
"resolution": "480p",
"ratio": "16:9",
"content": [
{"type": "image_url", "image_url": {"url": "https://example.com/cat.png"}, "role": "first_frame"}
]
}
}'
Poll a task
curl https://api.nightferry.io/v1/videos/task_8Qm2cVhK4pZr7TnW1yLs9XbE6dFa3JoU \
-H "Authorization: Bearer $NIGHTFERRY_KEY"
Poll no more often than once every 5 seconds. status is
queued, in_progress, completed or
failed; progress runs from 0 to 100.
{
"id": "task_8Qm2cVhK4pZr7TnW1yLs9XbE6dFa3JoU",
"object": "video",
"model": "doubao-seedance-2-0-mini-260615",
"status": "completed",
"progress": 100,
"created_at": 1790700000,
"completed_at": 1790700180
}
A failed task has status: failed and an error
object. Its message is the video service's own text, passed on as it came —
it may be in Chinese or English and may quote an internal task reference. Read
status, not the message.
GET /v1/tasks/{id} answers the same question in another shape:
status is QUEUED, IN_PROGRESS,
SUCCESS or FAILURE, progress is a
string such as "50%", and a failure carries
fail_reason.
A task that has not finished 24 hours after creation is marked failed, and its charge is released in full.
The video
curl https://api.nightferry.io/v1/tasks/task_8Qm2cVhK4pZr7TnW1yLs9XbE6dFa3JoU/artifacts \
-H "Authorization: Bearer $NIGHTFERRY_KEY"
{
"task_id": "task_8Qm2cVhK4pZr7TnW1yLs9XbE6dFa3JoU",
"artifacts": [
{
"key": "video",
"type": "video",
"content_url": "https://api.nightferry.io/v1/tasks/task_8Qm2cVhK4pZr7TnW1yLs9XbE6dFa3JoU/artifacts/video/content?access=…"
}
]
}
content_url needs no API key — hand it to a
browser or a player. Treat the URL as the
credential: anyone holding it can watch the video.
The video is available for about 24 hours after the task finishes. The link itself does not expire, but after that the video behind it is gone and the request fails. Download anything you mean to keep. We do not keep a copy.
With the key, GET /v1/videos/{id}/content returns the same
bytes. A task that also produced a last frame lists a second artifact,
last_frame.
USD billing and checking charges
The price columns on the price page are
alternatives: use “with reference video” when the request contains a video
reference, otherwise “no reference video”. The charge is
completion_tokens × the model’s USD price per million ÷ 1,000,000,
rounded to the nearest micro-dollar. For example, Mini 480p without a reference
video, with 40,594 completion tokens, costs $0.137303.
- At creation we hold an estimate from the resolution and
seconds, sized for a 16:9 frame at that resolution. Mini 480p for 4 seconds holds $0.129984. Insufficient credit refuses the create, and nothing is charged. - At completion the charge is settled at the reported
completion_tokens, and the difference to the hold is returned or taken. - A failed task is released in full.
- Two exceptions. If a finished task reports
total_tokensbut nocompletion_tokens, it is charged ontotal_tokens. If it reports no usage at all, the hold taken at creation stands as the charge.
Text and video share one account balance. Your balance and every charge, including each video task, are in Preview details.
Errors
A task that fails after it was accepted is reported with HTTP 200 and
status: failed, as above. The HTTP statuses below mean the request
itself was refused. Branch on the HTTP status, never on the message
text, which may be in Chinese or English.
| HTTP | Meaning |
|---|---|
| 400 | The request is invalid: a missing model, an unsupported resolution, a malformed body. GET /v1/videos/{id} also answers 400 for an unknown task. |
| 401 | Key missing or invalid. |
| 403 | Not enough available credit, or the key cannot use this model. |
| 404 | No such task on this account (GET /v1/tasks/{id}). |
| 429 | Too busy. Slow down and retry. |
| 5xx | The video service is temporarily unavailable. Keep the task ID if you have one and contact us. |
Testing and acceptance
- Create a task with the key for the channel you are testing. Record the
task
id, model and settings. - Poll until
completedorfailed. A pass iscompletedand a delivered video that meets your needs; an accepted create is not. - Fetch the video from
content_urlwithin 24 hours. - Match the task and its charge in Console.
If a result differs from what you expect, keep the task ID, model, channel, request time, HTTP status and task status so we can investigate. Do not include your API key.