Eternal AI API Documentation

Eternal AI Image-to-Video API

Eternal AI provides a REST API for image-to-video generation. Submit a still image plus a prompt, receive a request_id, then poll for the final video URL.

API host: https://open.eternalai.org. Do not send generation requests to https://eternalai.org; this website hosts the docs and developer dashboard only.

Core endpoints

Authentication

Use Authorization: Bearer sk_<your-key>. The api-key header is also accepted. Do not use x-api-key for V2 keys.

Minimal request body

{
  "prompt": "A cat slowly turning its head toward the camera",
  "image_url": "https://example.com/cat-start.jpg",
  "model_id": "wan-ai/wan2.2-i2v-a14b-lightning"
}

Developer facts for webfetch

These are the canonical machine-readable facts for the public /api route. The API is asynchronous: submit one job, store the request_id, then poll every 2-5 seconds until completion or failure.

Exact pricing

Billing is per second of generated output. 1 credit equals $1 USD. A default 5 second 720p generation costs $0.075.

Resolution$/secondCost of 5s clip
480p$0.005$0.025
580p$0.015$0.075
720p$0.015$0.075

Reliability and limits

Error response schema

{
  "status": false,
  "error": "human-readable error message",
  "result": null
}

Input constraints

Model and prompt details

Python submit, poll, download loop

import os
import time
import requests

BASE = "https://open.eternalai.org"
KEY = os.environ["ETERNAL_AI_API_KEY"]

def generate_video(prompt, image_url, timeout_s=270):
    submit = requests.post(
        f"{BASE}/api/image-to-video",
        headers={
            "Authorization": f"Bearer {KEY}",
            "Content-Type": "application/json",
        },
        json={
            "prompt": prompt,
            "image_url": image_url,
            "model_id": "wan-ai/wan2.2-i2v-a14b-lightning",
            "duration": "5",
            "resolution": "720p",
        },
        timeout=30,
    )
    submit.raise_for_status()
    request_id = submit.json()["result"]["request_id"]

    deadline = time.time() + timeout_s
    while time.time() < deadline:
        time.sleep(3)
        poll = requests.get(
            f"{BASE}/api/image-to-video/{request_id}/status",
            headers={"Authorization": f"Bearer {KEY}"},
            timeout=30,
        )
        poll.raise_for_status()
        result = poll.json()["result"]
        if result["status"] == "completed":
            return result["video_url"]
        if result["status"] == "failed":
            raise RuntimeError(result.get("error") or "generation failed")
    raise TimeoutError(f"video not ready: {request_id}")

JavaScript submit, poll, download loop

const BASE = 'https://open.eternalai.org';
const KEY = process.env.ETERNAL_AI_API_KEY;

export async function generateVideo(prompt, imageUrl) {
  const submit = await fetch(`${BASE}/api/image-to-video`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      prompt,
      image_url: imageUrl,
      model_id: 'wan-ai/wan2.2-i2v-a14b-lightning',
      duration: '5',
      resolution: '720p',
    }),
  });
  if (!submit.ok) {
    throw new Error(`submit failed: ${submit.status} ${await submit.text()}`);
  }
  const { result: { request_id } } = await submit.json();

  for (let attempts = 0; attempts < 90; attempts += 1) {
    await new Promise((resolve) => setTimeout(resolve, 3000));
    const poll = await fetch(
      `${BASE}/api/image-to-video/${request_id}/status`,
      { headers: { Authorization: `Bearer ${KEY}` } },
    );
    if (!poll.ok) {
      throw new Error(`poll failed: ${poll.status} ${await poll.text()}`);
    }
    const { result } = await poll.json();
    if (result.status === 'completed') return result.video_url;
    if (result.status === 'failed') {
      throw new Error(result.error || 'generation failed');
    }
  }
  throw new Error(`video not ready: ${request_id}`);
}

Request fields

FieldTypeRequiredDescription
promptstringrequiredText description of the video. Stay concise for best results.
image_urlstringrequiredStarting still (jpg/png/webp). Accepts a publicly reachable URL or a base64-encoded data URI (e.g. "data:image/jpeg;base64,…"). Maximum image file size is 15 MB.
model_idstringrequiredGeneration model. Must be in the server allowlist. Current: "wan-ai/wan2.2-i2v-a14b-lightning".
end_image_urlstringoptionalEnd frame for two-image conditioning. Accepts a public URL or a base64-encoded data URI. Default: null.
durationstringoptionalOutput length in seconds. Supported values: "1"–"5". Default: "5".
aspect_ratiostringoptional"auto","16:9", "9:16", "1:1", "4:3", "3:4". Default: "auto".
resolutionstringoptionalOutput resolution. Supported values: "480p", "580p", "720p". Default: "480p".
negative_promptstringoptionalThings to avoid. Default: "blur, distort, and low quality".
cfg_scalenumberoptionalCFG scale [0.0, 1.0]. Higher = closer to prompt. Default: 0.5.
seednumberoptionalDeterministic seed. Default: random.

Submit example

{
  "prompt": "A cat slowly turning its head toward the camera, soft natural light",
  "image_url": "https://example.com/cat-start.jpg",
  "model_id": "wan-ai/wan2.2-i2v-a14b-lightning",
  "end_image_url": "https://example.com/cat-end.jpg",
  "duration": "5",
  "aspect_ratio": "16:9",
  "resolution": "720p",
  "negative_prompt": "blur, low quality, watermark",
  "cfg_scale": 0.5,
  "seed": 12345
}

Submit response

{
  "status": true,
  "error": null,
  "result": {
    "request_id": "9a4f1c84-2bb3-4d29-a4e1-7c0d3b8f5a91"
  }
}

Poll response

{
  "status": true,
  "error": null,
  "result": {
    "request_id": "9a4f1c84-2bb3-4d29-a4e1-7c0d3b8f5a91",
    "status": "completed",
    "progress": 100,
    "video_url": "https://cdn.eternalai.org/video-webhook/9a4f1c84.mp4",
    "created_at": "2026-05-25T07:15:07Z"
  }
}

Status lifecycle

pending  ──►  processing  ──►  completed   (video_url set)
                          └►  failed      (error set, no video_url)

Billing

Resolution1 second5 seconds
480p0.005 credits0.025 credits
580p0.015 credits0.075 credits
720p0.015 credits0.075 credits

Submit errors

Poll errors

FAQ

API keys are bearer tokens.

Treat them like passwords; do not commit them to source control or expose them client-side.

Both Authorization and api-key headers are accepted.

Use whichever fits your HTTP client.

Polling cadence.

2–5 seconds is recommended. There is no separate billing for status calls.

Refunds.

If the backend rejects a submission, we refund automatically. If you cancel client-side after the job is accepted, we do NOT refund — the generation is already in flight.

Idempotency.

Submitting the same prompt+image twice creates two independent jobs and is billed twice. Use your own request deduplication if you want one-shot semantics.

Output retention.

Generated video_urls are served from a CDN and expire about 24 hours after generation. Download and re-host the result if you need long-term access.

Canonical API resources