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

# Quickstart (Beta)

> Submit a Video API job and poll it to completion in five minutes.

<Note>
  **Beta.** The Video API is in beta. Everything below runs against production.
</Note>

## 1. Get your API key

Create a key in the [dashboard](https://www.eachlabs.ai/api-keys) under **Settings** > **API Keys**. Every request authenticates with the `Authorization: Bearer YOUR_API_KEY` header.

## 2. Provide your input video

`input_url` (and `input_urls`) accept a **publicly downloadable `https` URL** to a media file — we stage your media into eachlabs storage automatically before processing. Private footage: a presigned URL from your own storage works — any `https` URL the engine can fetch is staged the same way (see [Acceptable Use](/video/acceptable-use)). `s3://` input URIs from your onboarding keep working unchanged, and you can chain jobs by passing a previous job's `output` URL as the next job's input. Local files: [upload them to eachlabs storage](/storage/upload-file) and pass the returned `public_url` directly as `input_url`.

<Note>
  The URL must point directly at a media file, identified by its file extension. The staged extensions are: video `.mp4` `.webm` `.mov` `.avi` `.mkv` `.flv`; audio `.mp3` `.wav` `.ogg` `.flac` `.m4a` `.aac` `.opus` `.wma`; image `.png` `.jpg` `.jpeg` `.gif` `.webp` `.bmp` `.tiff`; subtitle `.vtt` `.srt` `.ass`; color LUT `.cube`. A URL we cannot stage (unreachable, not public, or not identifiable as media) is rejected at \$0 with `PROVIDER_REJECTED` and the offending URL in the message.

  A URL with **no path extension at all** can declare its type with a `mime_type=` query parameter (the parameter is only consulted when the URL path carries no extension — it cannot override an unrecognized extension). The value is the MIME type with the separators encoded as underscores — the first `_` becomes `/`, any later `_` becomes `-`: `mime_type=video_mp4` → `video/mp4`, `video_quicktime` → `video/quicktime`, `audio_x_wav` → `audio/x-wav`. Any `video/*`, `audio/*`, or `image/*` type is accepted (the subtype is not checked against a list); non-media types are rejected. Note `.ts` artifacts (`hls_ladder` segments, `concat_copy` `container: "ts"`) are terminal today: `.ts` is not a staged extension and the extension blocks the `mime_type=` fallback, so they cannot be chained as inputs.
</Note>

<Warning>
  `.cube` LUTs stage only when the URL path carries the `.cube` extension — the `mime_type=` fallback accepts media types only, so an extensionless LUT URL is not staged. sendcmd scripts are not staged from public URLs at all: host them on eachlabs storage instead — an `s3://` URI from your onboarding or a `cdn-us.eachlabs.ai` URL.
</Warning>

## 3. Submit a job

Any capability from the [reference](/video/capabilities) works the same way — a request carries `capability`, `input_url`, and `params`.

<CodeGroup>
  ```bash cURL theme={"dark"}
  curl -X POST "https://api.eachlabs.ai/v1/prediction" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -d '{
      "model": "eachlabs-video-api",
      "input": {
        "capability": "transcode",
        "input_url": "YOUR_INPUT_URL",
        "params": { "vcodec": "h264", "crf": 28 }
      }
    }'
  ```

  ```python Python theme={"dark"}
  import requests

  resp = requests.post(
      "https://api.eachlabs.ai/v1/prediction",
      headers={"Authorization": "Bearer YOUR_API_KEY"},
      json={
          "model": "eachlabs-video-api",
          "input": {
              "capability": "transcode",
              "input_url": "YOUR_INPUT_URL",
              "params": {"vcodec": "h264", "crf": 28},
          },
      },
  )
  prediction_id = resp.json()["predictionID"]
  ```

  ```javascript JavaScript theme={"dark"}
  const resp = await fetch("https://api.eachlabs.ai/v1/prediction", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": "Bearer YOUR_API_KEY",
    },
    body: JSON.stringify({
      model: "eachlabs-video-api",
      input: {
        capability: "transcode",
        input_url: "YOUR_INPUT_URL",
        params: { vcodec: "h264", crf: 28 },
      },
    }),
  });
  const { predictionID } = await resp.json();
  ```
</CodeGroup>

A successful submit returns:

```json theme={"dark"}
{ "status": "success", "message": "Prediction created successfully", "predictionID": "d63b9b70-..." }
```

## 4. Poll until terminal

```bash theme={"dark"}
curl "https://api.eachlabs.ai/v1/prediction/PREDICTION_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

The typical flow is `created` → `processing` → **`success`**. The complete status set is `created`, `processing`, `success`, `error`, `cancelled` — a failed prediction reports `error`, never `"failed"`. `created` is the first status you see; the API never returns `starting`, so do not branch on it. Poll every 3-5 seconds: the platform's own status checks are quantized to \~5 s, so tighter loops buy nothing. Most short jobs finish in well under a minute (a 100-job burst of short transcodes drains in \~15-30 seconds). A prediction sitting in `created` unusually long is queued behind a concurrency cap — keep polling; see [Rate limits & concurrency](/video/billing-limits#rate-limits--concurrency).

```json theme={"dark"}
{
  "id": "1f0fdf4d-...",
  "input": {
    "capability": "transcode",
    "input_url": "https://your-host.example/source.mp4",
    "params": { "vcodec": "h264", "crf": 28 }
  },
  "logs": null,
  "status": "success",
  "output": ["https://cdn-us.eachlabs.ai/uploads/9b967c93-....mp4"],
  "metrics": { "predict_time": 194.86, "cost": 0.2865, "tier": "cpu", "billed_seconds": 191, "output_gb": 0.0024 },
  "urls": {
    "get": "https://api.eachlabs.ai/v1/prediction/1f0fdf4d-...",
    "cancel": "https://api.eachlabs.ai/v1/prediction/1f0fdf4d-.../cancel"
  }
}
```

* `output` — hosted artifact URL(s) for media capabilities, or a structured JSON object for the analysis capabilities (`probe`, `scene_detect`, `silence_detect`, `audio_analysis`). The exact JSON per output class is in [Response shapes](/video/capabilities#response-shapes).
* `metrics.cost` — what the job actually billed, in USD: `billed_seconds` × $0.0015, plus delivery on `output_gb` beyond the 200 MB free allowance per job ($0.30/GB on the excess) — see [the formula](/video/billing-limits#the-formula). Rejected, failed, and cancelled jobs bill `0` (see [Failed compute](/video/billing-limits#failed-compute) for how failures are metered).
* `metrics.billed_seconds`, `metrics.output_gb`, `metrics.tier` — the billed quantities, present on every successful prediction. Failed predictions carry only `cost` and `predict_time`.
* `metrics.predict_time` — the prediction's **full wall time** in seconds, from processing start to completion detection, including internal queueing and poll quantization. It is **not** the billed quantity: cost comes from `metrics.billed_seconds` × $0.0015. In the example above `billed_seconds` reads 191 → `191 × $0.0015 = \$0.2865`, while `predict\_time\` reads 194.86 — see [the formula](/video/billing-limits#the-formula).
* `input` — your submitted input, echoed back. `logs` is present on every poll and is `null` today.
* `urls.get` — this prediction's poll URL. `urls.cancel` — its cancel endpoint (`POST`, same `Authorization` header); cancellation is record-only and a cancelled prediction never bills — see [Cancelling a prediction](/video/billing-limits#cancelling-a-prediction).
* On `status: "error"` the `output` object carries `error_code` and `error_message` — except on a dispatch failure at submit, which writes a minimal envelope carrying only `error`, so guard for their absence before reading them. The field-by-field envelope with two real captured examples, and the billing impact per class, are in the [error catalog](/video/billing-limits#error-catalog).
* Download or persist artifacts promptly; treat artifact URLs as valid for at least 24 hours.

## 5. Optional: completion webhooks

Instead of polling, pass the platform's standard `webhook_url` when creating the prediction and receive a callback on completion — see the [webhooks documentation](/api/webhooks/overview).

## Next steps

<CardGroup cols={2}>
  <Card title="Capabilities Reference" icon="list" href="/video/capabilities">
    Every capability with parameters, request bodies, and response shapes.
  </Card>

  <Card title="Billing & Limits" icon="credit-card" href="/video/billing-limits">
    The billing formula, the error catalog, and the operational limits.
  </Card>
</CardGroup>

Policies: [Acceptable Use](/video/acceptable-use) · [Abuse & Takedown](/video/abuse-and-takedown) · [Versioning & Deprecation](/video/versioning)
