> ## Documentation Index
> Fetch the complete documentation index at: https://www.tryleap.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Leap's API is at https://api.tryleap.ai. Send the key in the x-api-key header or as Authorization: Bearer; keys start with leap_.
> Take model IDs and input schemas from GET /v1/models/{creator}/{name}, or from https://api.tryleap.ai/v1/public/models without a key. Never guess an input field: unknown fields are a 400.
> Model inputs take uploaded file IDs (file_...), never URLs.
> To give a coding agent the whole API, install the Leap skill: curl -fsSL https://www.tryleap.ai/install.sh | sh
> The full OpenAPI 3.1 spec is at https://www.tryleap.ai/docs/openapi.json.

# Quickstart

> Create an API key, make an image in one request and save it, then start a video and poll it until it's done.

<Tip>
  No key yet? Browse models and schemas at [https://api.tryleap.ai/v1/public/models](https://api.tryleap.ai/v1/public/models). It needs no key, and it lists every model ID with its price and input schema.
</Tip>

<Steps titleSize="h2">
  <Step title="Create a key">
    Open [app.tryleap.ai/go/api](https://app.tryleap.ai/go/api), sign in, and create a key. New workspaces start with \$2 of free credit, and the examples on this page cost about \$0.69 in all, so you can try them before you add a card. Limits against abuse can withhold the credit, for example after several sign-ups from one network on the same day.

    ```bash theme={"theme":"css-variables"}
    export LEAP_API_KEY="leap_..."
    ```

    Keep the key on your server. It spends your workspace's balance.
  </Step>

  <Step title="Make an image">
    `prefer: wait=60` holds the request open until the image is ready, for up to 60 seconds, so one request gets you the result.

    To run the Node.js version, save it as `quickstart.mjs` and run `node quickstart.mjs` (Node 18 or later). For the Python version, run `pip install requests` first. The curl version needs [jq](https://jqlang.org).

    <CodeGroup>
      ```bash curl theme={"theme":"css-variables"}
      RESPONSE=$(curl -s https://api.tryleap.ai/v1/generations \
        -H "x-api-key: $LEAP_API_KEY" \
        -H "content-type: application/json" \
        -H "prefer: wait=60" \
        -d '{
          "model": "black-forest-labs/flux-2-pro",
          "input": { "prompt": "A silk scarf in the wind on a ferry deck", "aspect_ratio": "3:4" }
        }')

      echo "$RESPONSE" | jq .
      ```

      ```js Node.js theme={"theme":"css-variables"}
      import { writeFile } from "node:fs/promises";

      const response = await fetch("https://api.tryleap.ai/v1/generations", {
        method: "POST",
        headers: {
          "x-api-key": process.env.LEAP_API_KEY ?? "",
          "content-type": "application/json",
          prefer: "wait=60",
        },
        body: JSON.stringify({
          model: "black-forest-labs/flux-2-pro",
          input: { prompt: "A silk scarf in the wind on a ferry deck", aspect_ratio: "3:4" },
        }),
      });

      const generation = await response.json();
      if (!response.ok) throw new Error(generation.error.message);

      if (generation.status === "succeeded") {
        const { url } = generation.output[0];
        console.log(url);

        // The link is signed, so downloading it needs no API key.
        const image = await fetch(url);
        if (!image.ok) throw new Error(`Download failed: ${image.status}`);
        await writeFile("scarf.jpg", Buffer.from(await image.arrayBuffer()));
      } else if (generation.status === "failed") {
        console.error(generation.error.message);
        process.exitCode = 1;
      } else {
        // Still queued or running when the wait ended: poll it until it's done.
        console.log(`${generation.id} is ${generation.status}. Poll GET /v1/generations/${generation.id}.`);
      }
      ```

      ```python Python theme={"theme":"css-variables"}
      import os

      import requests

      response = requests.post(
          "https://api.tryleap.ai/v1/generations",
          headers={"x-api-key": os.environ["LEAP_API_KEY"], "prefer": "wait=60"},
          json={
              "model": "black-forest-labs/flux-2-pro",
              "input": {"prompt": "A silk scarf in the wind on a ferry deck", "aspect_ratio": "3:4"},
          },
          timeout=90,
      )
      generation = response.json()
      if not response.ok:
          raise RuntimeError(generation["error"]["message"])

      if generation["status"] == "succeeded":
          url = generation["output"][0]["url"]
          print(url)

          # The link is signed, so downloading it needs no API key.
          image = requests.get(url, timeout=60)
          image.raise_for_status()
          with open("scarf.jpg", "wb") as f:
              f.write(image.content)
      elif generation["status"] == "failed":
          raise SystemExit(generation["error"]["message"])
      else:
          # Still queued or running when the wait ended: poll it until it's done.
          print(f"{generation['id']} is {generation['status']}. Poll GET /v1/generations/{generation['id']}.")
      ```
    </CodeGroup>
  </Step>

  <Step title="Read and save the result">
    The answer is a generation. Its `output` holds signed links to the files:

    ```json theme={"theme":"css-variables"}
    {
      "id": "gen_4cB8rTq2LmX9vK1nW7pZs3Ad",
      "object": "generation",
      "model": "black-forest-labs/flux-2-pro",
      "modality": "image",
      "status": "succeeded",
      "input": { "prompt": "A silk scarf in the wind on a ferry deck", "aspect_ratio": "3:4", "n": 1 },
      "output": [
        {
          "type": "image",
          "url": "https://api.tryleap.ai/files/eyJrIjoibWVkaWEv...",
          "expires_at": "2026-10-05T18:22:47.120Z",
          "content_type": "image/jpeg",
          "width": 896,
          "height": 1152
        }
      ],
      "error": null,
      "usage": { "cost_usd": "0.033" },
      "created_at": "2026-10-04T18:22:41.512Z",
      "completed_at": "2026-10-04T18:22:47.120Z"
    }
    ```

    Anyone with the `url` can download the file until `expires_at`, without an API key. Links expire after 24 hours, so download what you keep. The Node.js and Python scripts above save the image as `scarf.jpg`. With curl:

    ```bash theme={"theme":"css-variables"}
    curl -L -o scarf.jpg "$(echo "$RESPONSE" | jq -r '.output[0].url')"
    ```

    `-L` follows the redirect to storage that large files, such as videos, get. Reading the generation again with `GET /v1/generations/{id}` signs fresh links. The full object is in [Generations](/docs/generations#the-generation-object).
  </Step>

  <Step title="Make a video">
    Video takes a minute or more, longer than one request can wait. This script starts a clip without waiting, then long-polls it: each read waits up to 60 seconds for the clip to finish. It gives up after 15 minutes, and saves the clip as `heron.mp4`. Save it as `video.sh` and run `bash video.sh`. It needs jq 1.6 or later.

    ```bash theme={"theme":"css-variables"}
    #!/usr/bin/env bash
    set -euo pipefail

    API=https://api.tryleap.ai

    RESPONSE=$(curl -sS "$API/v1/generations" \
      -H "x-api-key: $LEAP_API_KEY" \
      -H "content-type: application/json" \
      -d '{"model": "google/veo-3.1-fast", "input": {"prompt": "A heron lands on a still lake at dawn", "duration": 4}}')

    # An error answer has no id: print its message and stop.
    GENERATION_ID=$(jq -r '.id // empty' <<< "$RESPONSE")
    if [ -z "$GENERATION_ID" ]; then
      jq -r '.error.message // .' <<< "$RESPONSE" >&2
      exit 1
    fi
    echo "Started $GENERATION_ID"

    DEADLINE=$(( $(date +%s) + 15 * 60 ))
    while true; do
      # Each read waits up to 60 seconds for the run to end. A dropped request or an
      # error answer leaves STATUS empty, and the loop reads again.
      GENERATION=$(curl -sS --max-time 75 "$API/v1/generations/$GENERATION_ID" \
        -H "x-api-key: $LEAP_API_KEY" -H "prefer: wait=60" || true)
      STATUS=$(jq -r '.status // empty' <<< "$GENERATION" 2> /dev/null || true)

      case "$STATUS" in
        succeeded) break ;;
        failed | canceled)
          echo "$STATUS: $(jq -r '.error.message // "no message"' <<< "$GENERATION")" >&2
          exit 1
          ;;
      esac

      if [ "$(date +%s)" -ge "$DEADLINE" ]; then
        echo "$GENERATION_ID is still running. Read it again later." >&2
        exit 1
      fi
      sleep 2
    done

    curl -sSL -o heron.mp4 "$(jq -r '.output[0].url' <<< "$GENERATION")"
    echo "Saved heron.mp4"
    ```

    This 4-second clip with sound costs \$0.66. [Generations](/docs/generations#long-poll) shows the same long-poll in TypeScript and Python.
  </Step>
</Steps>

## Next

<CardGroup cols={2}>
  <Card title="Find a model" icon="list" href="/docs/models">
    Browse the catalog and read what each model takes.
  </Card>

  <Card title="Send a photo" icon="image" href="/docs/files">
    Upload a file, then animate or edit it.
  </Card>

  <Card title="Turn a photo into a video" icon="clapperboard" href="/docs/examples/photo-to-video">
    Upload a product photo, animate it with Veo and save the clip.
  </Card>

  <Card title="Add generation to a Next.js app" icon="code" href="/docs/examples/nextjs">
    Start runs from a route handler and show the result on a page.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.