Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.skills.video/llms.txt

Use this file to discover all available pages before exploring further.

This quick start shows how to call skills.video with a minimal fetch request: create a google-veo-3.1 generation with a prompt, receive an id, and poll until it finishes.

Send your first request

Use any runtime that supports fetch. Replace placeholders before running.
const API_KEY = "<YOUR_API_KEY>";
const BASE_API_URL = "https://open.skills.video/api";

async function createTask() {
  const res = await fetch(`${BASE_API_URL}/v1/generation/google/veo-3.1`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      prompt: "sunrise over mountains in watercolor style"
    })
  });

  if (!res.ok) {
    throw new Error(`create failed: ${res.status} ${await res.text()}`);
  }

  const data = await res.json(); // { id, status, ... }
  console.log("created task:", data.id);
  return data.id;
}

Upload files with multipart/form-data

Use file upload when a model needs local image, video, or audio input. JSON requests are enough for text and URLs, but they cannot carry raw file bytes directly. For file inputs, switch to multipart/form-data and send files in the exact field names defined by that model’s OpenAPI schema. For example, POST /v1/generation/google/nano-banana-pro accepts image_urls[], and each item can be either a public URL or a binary file.
curl -X POST "https://open.skills.video/api/v1/generation/google/nano-banana-pro" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Accept: application/json" \
  -F "prompt=Turn this sketch into a clean product mockup" \
  -F "image_urls=@./assets/sketch.png" \
  -F "image_urls=https://images.example.com/reference.jpg" \
  -F "aspect_ratio=1:1" \
  -F "resolution=2K"
If you use fetch in Node.js, send a FormData body and append files with the same field key.
import { readFile } from "node:fs/promises";

const API_KEY = "<YOUR_API_KEY>";
const BASE_API_URL = "https://open.skills.video/api";

async function createImageTaskWithFile() {
  const bytes = await readFile("./assets/sketch.png");

  const form = new FormData();
  form.append("prompt", "Turn this sketch into a clean product mockup");
  form.append("image_urls", new Blob([bytes], { type: "image/png" }), "sketch.png");
  form.append("aspect_ratio", "1:1");
  form.append("resolution", "2K");

  const res = await fetch(`${BASE_API_URL}/v1/generation/google/nano-banana-pro`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`
    },
    body: form
  });

  if (!res.ok) {
    throw new Error(`create failed: ${res.status} ${await res.text()}`);
  }

  return res.json();
}
If a model schema defines a direct file field, the upload shape is:
-F "file=@./path/to/input.png"
form.append("file", new Blob([bytes], { type: "image/png" }), "input.png");
Use the same pattern for other models: check that model’s OpenAPI schema for the exact file-capable fields, accepted MIME types, and limits.

Poll for the result

Poll the generation by id until it completes or fails.
const API_KEY = "<YOUR_API_KEY>";
const BASE_API_URL = "https://open.skills.video/api";

async function pollTask(id: string) {
  for (let i = 0; i < 20; i++) {
    const res = await fetch(`${BASE_API_URL}/v1/generation/${id}`, {
      headers: { Authorization: `Bearer ${API_KEY}` }
    });

    if (!res.ok) {
      throw new Error(`poll failed: ${res.status} ${await res.text()}`);
    }

    const task = await res.json(); // { status, outputUrl?, error? }

    if (task.status === "succeeded") {
      console.log("done:", task.outputUrl ?? task);
      return task;
    }

    if (task.status === "failed") {
      throw new Error(`task failed: ${task.error ?? "unknown error"}`);
    }

    await new Promise((r) => setTimeout(r, 3000));
  }

  throw new Error("polling timed out");
}

// Example usage
(async () => {
  const id = await createTask();
  await pollTask(id);
})();

Use SSE instead of polling (optional)

Use SSE when you need near real-time status updates in one long-lived response, and want to avoid repeated polling calls. For full request and response details, see SSE Streaming. If a model uses POST /v1/generation/{provider}/{model}, its SSE form is:
POST /v1/generation/sse/{provider}/{model}
For example:
curl -N -X POST "https://open.skills.video/api/v1/generation/sse/google/veo-3.1" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
    "prompt": "sunrise over mountains in watercolor style"
  }'
Typical stream output:
data: {"id":"<GENERATION_ID>","status":"IN_QUEUE"}
data: {"id":"<GENERATION_ID>","status":"IN_PROGRESS"}
data: {"id":"<GENERATION_ID>","status":"COMPLETED","data":{"<OUTPUT_FIELD>":"<OUTPUT_VALUE>"}}