API

One resource: a generation. Create it, poll it, download the mp4. Base URL https://api.highlander.sh — the same routes are served from https://highlander.sh.

Authentication

Every request carries a bearer key from your dashboard. Keys look like hl_live_…, are shown once, and are stored only as a hash — if you lose one, revoke it and make another.

header
Authorization: Bearer hl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

POST /v1/generations

Returns 202 immediately with a job id. Credit is reserved at this point, at $0.02 per second of output video ($0.29 for a full 14.375 s clip), and returned if the job does not produce a video.

request
curl -X POST https://api.highlander.sh/v1/generations \
  -H "Authorization: Bearer $HIGHLANDER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Waves break over black volcanic rock at dusk.","frames":345,"steps":5}'
FieldTypeNotes
promptstring, requiredUp to 4000 characters. Describes shot, subject, camera and light.
negative_promptstringOptional. Defaults to empty.
framesint, default 345124..345, and must satisfy frames % 17 == 5 — H3's causal VAE packs 17 frames into 5 latents, and the model floors at 5 s. The warmed presets are 124 / 243 / 345 (5.17 / 10.13 / 14.38 s — the legal counts closest to 5/10/15 s; exactly 15 s does not exist, 14.375 s is the model's ceiling). Other legal counts work but pay a one-time multi-minute compile.
first_frame_urlstring (first-frame mode)Public http(s) URL of an image that becomes the clip's first frame. Optional last_frame_url pins the final frame too. Only accepted while the server is in first-frame mode — see Modes.
referencesarray (reference mode)Ordered [{url, type}] with type image | video | audio (max 9 / 3 / 3, 12 total). The prompt addresses them positionally: <Subject 1>, <Picture 2>, … Order is meaningful. Only accepted in reference mode — see Modes.
stepsint, default 52..50. The deployed schedule is tuned for 5. Steps are where the compute goes, so price scales with them: a step count of n costs (n-1)/4 of the listed per-second rate. The default is 1.0x.
seedint, default 1000Same seed and prompt reproduce the same clip.
width / height1344 / 768Fixed. The pipeline is compiled for one resolution; anything else is rejected rather than silently recompiled.

Modes

One 8×H100 node serves one pipeline at a time. Which fields your request carries selects the pipeline it needs: none of the media fields means text-to-video, first_frame_url means first-frame conditioning, references means subject references. If the server is currently in a different mode you get 409 mode_unavailable and are not charged. GET /v1/health reports the active mode.

t2v (default)text → video + audioThe realtime configuration: a warm generation takes ~13.5 s for a 14.375 s clip.
fl2vfirst-frame image → video + audioYour image becomes the clip's first frame (optionally the last too). Distilled 4-step pipeline; latency comparable to t2v.
refsubject references → video + audioPuts specific subjects from your media into the clip. Undistilled 50-step pipeline — expect roughly a minute per 5 s clip, priced by the same steps rule.
first-frame request
curl -X POST https://api.highlander.sh/v1/generations \
  -H "Authorization: Bearer $HIGHLANDER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"The painting comes alive; she turns and smiles.",
       "first_frame_url":"https://example.com/portrait.png","frames":124}'
reference request
curl -X POST https://api.highlander.sh/v1/generations \
  -H "Authorization: Bearer $HIGHLANDER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"<Subject 1> walks through a sunlit plaza, camera tracking.",
       "references":[{"url":"https://example.com/person.jpg","type":"image"}],
       "frames":124,"steps":50}'

GET /v1/generations/{id}

Poll every couple of seconds. status moves queued → running → succeeded | failed. A warm worker finishes in ~13.5 s; a cold one loads 144 GB of weights and warms compiled kernels first, which takes several minutes.

response
{
  "id": "0f7c1e2a-...",
  "status": "succeeded",
  "prompt": "A traceur vaults a rooftop ledge...",
  "frames": 345,
  "steps": 5,
  "seed": 1000,
  "width": 1344,
  "height": 768,
  "duration_s": 14.375,
  "cost_usd": 0.29,
  "refunded": false,
  "latency_s": 13.506,
  "realtime_factor": 0.9395,
  "error": null,
  "video": "/v1/generations/0f7c1e2a-.../video",
  "poll": "/v1/generations/0f7c1e2a-..."
}

GET /v1/generations/{id}/video

Streams the mp4 (1344x768, 24 fps, AAC stereo generated in the same pass). Returns 409 until the job has succeeded.

Examples

python
import os, time, requests

BASE = "https://api.highlander.sh"
H = {"Authorization": f"Bearer {os.environ['HIGHLANDER_KEY']}"}

job = requests.post(f"{BASE}/v1/generations", headers=H, json={
    "prompt": "A traceur vaults a rooftop ledge into a roll, tracking shot alongside.",
    "frames": 345,      # 14.375 s
    "steps": 5,
}).json()

while True:
    s = requests.get(f"{BASE}/v1/generations/{job['id']}", headers=H).json()
    if s["status"] in ("succeeded", "failed"):
        break
    time.sleep(2)

if s["status"] == "failed":
    raise SystemExit(s["error"])

mp4 = requests.get(f"{BASE}/v1/generations/{job['id']}/video", headers=H).content
open("out.mp4", "wb").write(mp4)
print(f"{s['latency_s']}s render, {s['realtime_factor']}x realtime, ${s['cost_usd']}")
typescript
const BASE = "https://api.highlander.sh";
const H = { Authorization: `Bearer ${process.env.HIGHLANDER_KEY}`,
            "Content-Type": "application/json" };

const job = await fetch(`${BASE}/v1/generations`, {
  method: "POST",
  headers: H,
  body: JSON.stringify({ prompt: "Waves break over black volcanic rock at dusk." }),
}).then((r) => r.json());

let status;
do {
  await new Promise((r) => setTimeout(r, 2000));
  status = await fetch(`${BASE}/v1/generations/${job.id}`, { headers: H })
    .then((r) => r.json());
} while (status.status === "queued" || status.status === "running");

const mp4 = await fetch(`${BASE}/v1/generations/${job.id}/video`, { headers: H });
await Bun.write("out.mp4", await mp4.arrayBuffer());

Errors

401unauthorizedMissing or revoked key.
402insufficient_creditsThe quote exceeds your balance. Nothing was queued.
409mode_unavailableThe request needs a different mode than the server is serving. Nothing was queued or charged; check GET /v1/health for the active mode.
422invalid_requestBad frame count, prompt too long, unsupported resolution.
429too_many_active_jobsYou already have 2 generations in flight.
503inference_unavailableInference is switched off or the worker is unreachable. You are not charged.
502upstream_errorThe worker rejected the job. Any reservation is refunded.

Errors are JSON with error and message. Anything that fails to produce a video refunds its reservation, so a retry loop cannot silently drain a balance.

Limits and status

  • 2 concurrent generations per account. The deployment serves one job at a time.
  • — Maximum clip length 14.375 s (345 frames).
  • GET /v1/health is public and reports whether inference is currently enabled and how deep the queue is.
  • GET /v1/config (authenticated) returns the deployed model configuration and your balance.