REST API

POST

/api/v1/videos/generations

Start a video job with a text prompt (and, for image-to-video models, a start image). The call returns at once with a job id; the finished video comes from GET /api/v1/jobs/{id}. Videos take from about a minute to several minutes.

URL
https://railwail.com/api/v1/videos/generations
Key scope
video
Returns
202 + job id
Billing
held at start, refunded on failure

How it works

  1. 1

    POST the job

    The API checks key, scope and balance, holds the price of the run and hands the job to the provider. The answer is HTTP 202 with job_id and a Location header.
  2. 2

    Poll the job

    GET /api/v1/jobs/{job_id} every 5–10 seconds. Each poll asks the provider for news and counts toward your key's rate limit, so polling faster only uses up requests.
  3. 3

    Download the result

    When status is completed, output_url holds the video. It can be a temporary provider link, so store the file yourself. On failed or cancelled the held credits are refunded.

Request body

JSON. Unknown fields are rejected with 400 validation_failed. Which optional fields a model actually uses depends on the model; its page lists its inputs.

modelrequired
string
Model slug, e.g. kling-v2-1 or veo-3-1-fast.
promptrequired
string
What should happen in the video. Up to 8,000 characters.
negative_prompt
stringmodel-dependent
What to avoid.
duration_sec
numbermodel-dependent
Length in seconds, 1–60. The price of per-second models scales with it.
aspect_ratio
stringmodel-dependent
16:9, 9:16, 1:1, 4:3, 3:4 or 21:9.
resolution
stringmodel-dependent
480p, 720p, 1080p or 4k.
fps
integermodel-dependent
8–60.
seed
integer
Fixed seed for repeatable output where the model supports it.
image_url / start_image
string (URL)
First frame for image-to-video models. Both names mean the same image; the model's page lists whether it takes one.
end_image
string (URL)
Last frame, for models that take one. Only together with image_url or start_image (otherwise 400 end_image_without_start_image).
user
string
Your own end-user id, stored with the job.

Example

There is no video method in the OpenAI SDKs or the railwail npm SDK; use plain HTTP. Put your key in RAILWAIL_API_KEY (it needs the video scope).

// Node 18+ (global fetch). ESM: save as video.mjs
const BASE = "https://railwail.com/api/v1";
const headers = {
  Authorization: `Bearer ${process.env.RAILWAIL_API_KEY}`,
  "Content-Type": "application/json",
};

const res = await fetch(`${BASE}/videos/generations`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    model: "kling-v2-1",
    prompt: "A slow drone shot over a misty pine forest at sunrise",
  }),
});
if (!res.ok) throw new Error((await res.json()).error?.message);
const { job_id } = await res.json();

const deadline = Date.now() + 15 * 60_000; // give up after 15 minutes
while (Date.now() < deadline) {
  const job = await (await fetch(`${BASE}/jobs/${job_id}`, { headers })).json();
  if (job.status === "completed") {
    console.log(job.output_url);
    break;
  }
  if (job.status === "failed" || job.status === "cancelled") throw new Error(job.error ?? job.status);
  await new Promise((r) => setTimeout(r, 8000));
}

Response (202)

Shape of the answer; values are placeholders.

JSON
{
  "job_id": "<uuid>",
  "id": "<uuid>",
  "object": "video.generation",
  "status": "queued",
  "model": "kling-v2-1",
  "provider_job_id": "<provider id>",
  "estimated_duration_seconds": <number>,
  "created_at": <unix seconds>,
  "cost": { "credits": <number>, "currency": "credits" }
}

estimated_duration_seconds is a rough guide for when to start polling, not a promise. cost.credits is the amount held (1 credit = USD 0.01). The finished job is described on the Jobs API page.

Models you can call here

Live from the catalog: video models this endpoint can run right now, with the price as charged.

Errors and limits

StatusCodeWhat to do
400validation_failedA field is unknown or out of range; details lists which.
400unsupported_image_inputAn image field the model has no input for. Nothing was charged.
400end_image_without_start_imageend_image without image_url or start_image. Nothing was charged.
402insufficient_creditsTop up on the billing page.
402monthly_limit_exceededYour monthly spending limit is reached (chat uses spending_limit_reached for the same case).
403insufficient_scopeThe key lacks the video scope.
404model_not_foundCheck the slug in the list above.
429trial_limitTrial accounts may reserve at most 2 credits per run; most videos cost more. Top up to lift it.
503model_unavailableThe model has no verified price right now; pick another.

Rate limit headers

The 202 answer and errors after the key check carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. See Rate limits and Error codes.

Try a model in the browser first: every video model page has a playground and shows the price of a default run.