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

# Quickstart

> Go from a video URL to a captioned video in three steps.

You need a [CaptionCraft API key](/authentication), prepaid seconds, and a [supported video URL](/media-requirements). The shell examples use `curl` and `jq`.

<Steps>
  <Step title="Configure your environment">
    <Note>
      Use the endpoint provided with your API key.
    </Note>

    ```bash theme={"system"}
    export CAPTIONCRAFT_API_URL="https://api.captioncraft.studio"
    # Set CAPTIONCRAFT_API_KEY securely in your environment.
    ```

    Set `CAPTIONCRAFT_API_KEY` through your secret manager or shell environment. Replace the sample URL below with your own direct video URL:

    ```bash theme={"system"}
    export VIDEO_URL="https://your-cdn.example/video.mp4"
    export IDEMPOTENCY_KEY="$(uuidgen)"
    ```

    Keep the same idempotency key when retrying this request. Use a new key for each new video or changed input.
  </Step>

  <Step title="Submit your video">
    This example reserves up to 60 seconds of prepaid credit. Choose a limit at least as long as your video, up to 600 seconds.

    <CodeGroup>
      ```bash cURL theme={"system"}
      jq -n --arg video_url "$VIDEO_URL" '{
        video_url: $video_url,
        preset: "highlight",
        max_duration_seconds: 60,
        style: { highlight_color: "#7651E8" }
      }' > request.json

      curl --fail-with-body --silent --show-error \
        "$CAPTIONCRAFT_API_URL/v1/subtitles" \
        -H "Authorization: Bearer $CAPTIONCRAFT_API_KEY" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
        --data-binary @request.json > submission.json

      cat submission.json
      ```

      ```javascript JavaScript theme={"system"}
      // Node.js 20+. Set VIDEO_URL, IDEMPOTENCY_KEY, and API environment variables.
      const response = await fetch(`${process.env.CAPTIONCRAFT_API_URL}/v1/subtitles`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.CAPTIONCRAFT_API_KEY}`,
          "Content-Type": "application/json",
          "Idempotency-Key": process.env.IDEMPOTENCY_KEY,
        },
        body: JSON.stringify({
          video_url: process.env.VIDEO_URL,
          preset: "highlight",
          max_duration_seconds: 60,
          style: { highlight_color: "#7651E8" },
        }),
      });
      const job = await response.json();
      if (!response.ok) throw new Error(JSON.stringify(job.error));
      console.log(job);
      ```

      ```python Python theme={"system"}
      # Python 3; uses only the standard library.
      import json
      import os
      import urllib.request

      request = urllib.request.Request(
          f"{os.environ['CAPTIONCRAFT_API_URL']}/v1/subtitles",
          data=json.dumps({
              "video_url": os.environ["VIDEO_URL"],
              "preset": "highlight",
              "max_duration_seconds": 60,
              "style": {"highlight_color": "#7651E8"},
          }).encode(),
          headers={
              "Authorization": f"Bearer {os.environ['CAPTIONCRAFT_API_KEY']}",
              "Content-Type": "application/json",
              "Idempotency-Key": os.environ["IDEMPOTENCY_KEY"],
          },
          method="POST",
      )
      with urllib.request.urlopen(request) as response:
          job = json.load(response)
      print(job)
      ```
    </CodeGroup>

    A successful submission returns **202 Accepted**:

    ```json theme={"system"}
    {
      "id": "<JOB_ID>",
      "status": "ingesting",
      "replayed": false,
      "status_url": "<API_BASE_URL>/v1/jobs/<JOB_ID>"
    }
    ```

    This confirms the job was accepted. It does not mean the video is ready.
  </Step>

  <Step title="Poll and download">
    Using the cURL submission above, poll until the job reaches a terminal state:

    ```bash theme={"system"}
    STATUS_URL=$(jq -er '.status_url' submission.json)

    while true; do
      sleep 5
      curl --fail-with-body --silent --show-error "$STATUS_URL" \
        -H "Authorization: Bearer $CAPTIONCRAFT_API_KEY" > job.json || break

      STATUS=$(jq -r '.status' job.json)
      case "$STATUS" in
        completed)
          curl --fail --output captioned.mp4 "$(jq -er '.video.url' job.json)"
          curl --fail --output subtitles.srt "$(jq -er '.subtitles.srt_url' job.json)"
          curl --fail --output transcript.json "$(jq -er '.subtitles.transcript_url' job.json)"
          break
          ;;
        failed|canceled)
          cat job.json
          break
          ;;
        *) printf '%s\n' "Job status: $STATUS" ;;
      esac
    done
    ```

    Download URLs do not need the API key. Save all three files within 24 hours of completion. If a poll returns `429`, wait at least the `Retry-After` interval and retry that poll.
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Customize your captions" icon="palette" href="/caption-styles">Explore presets, positioning, and colors.</Card>
  <Card title="Handle job states" icon="workflow" href="/job-lifecycle">Add progress, cancellation, and safe retries.</Card>
</CardGroup>
