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.

Available by arrangement, not yet self-serve. Video needs a video key, and each video key serves one channel: domestic, or overseas. Ask us for the key you need, or for one of each. A text key cannot call video models.

Endpoint

Base URLhttps://api.nightferry.io
CreatePOST /v1/videos
PollGET /v1/videos/{id}
Task detailGET /v1/tasks/{id}
Result linkGET /v1/tasks/{id}/artifacts
Auth headerAuthorization: Bearer <your video key>
Task id prefixtask_

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

Limits per model
Model idResolutionDuration (s)ImagesVideosAudio
doubao-seedance-2-0-260128480p / 720p / 1080p / 4k4–15 or −1933
doubao-seedance-2-0-fast-260128480p / 720p4–15 or −1933
doubao-seedance-2-0-mini-260615480p / 720p4–15 or −1933
doubao-seedance-2-5-260628480p / 720p / 1080p4–30 or −1301010

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
}
FieldMeaning
modelRequired. A model id from the table above.
promptThe text prompt.
secondsOutput duration in seconds. Omitted, the charge held at creation assumes 15 seconds.
metadataThe 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"}
    ]
  }
}'
Every create is a new task, and a new charge. There is no idempotency key. If a create times out on your side, poll before you resubmit — resubmitting can start, and pay for, a second video. There are no webhooks and there is no cancel: a submitted task runs to completion.

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.

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.

HTTPMeaning
400The request is invalid: a missing model, an unsupported resolution, a malformed body. GET /v1/videos/{id} also answers 400 for an unknown task.
401Key missing or invalid.
403Not enough available credit, or the key cannot use this model.
404No such task on this account (GET /v1/tasks/{id}).
429Too busy. Slow down and retry.
5xxThe video service is temporarily unavailable. Keep the task ID if you have one and contact us.

Testing and acceptance

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.