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

# Code Examples

> Complete Python and JavaScript examples for all FastDrop API operations

<Note>
  These examples use `?wait=N`, which holds the request open until the job finishes — one or two requests per job instead of one every few seconds. If your service can receive HTTP callbacks, prefer [webhooks](/guides/webhooks) and skip waiting altogether. See [Getting your results](/guides/delivery-modes).
</Note>

## Check whether footage is usable

Answers in the same request. Call it before anything expensive — a `reject` here
saves the whole run.

<CodeGroup>
  ```python Python theme={null}
  import httpx

  API_KEY = "fd_live_your_key_here"
  BASE_URL = "https://api.fastdrop.io/api/v1"
  HEADERS = {"X-API-Key": API_KEY}

  response = httpx.post(
      f"{BASE_URL}/readiness",
      headers=HEADERS,
      json={
          "video_url": "https://example.com/take_04.mp4",
          "context": "motion_analysis",
      },
      timeout=30,
  )
  response.raise_for_status()
  result = response.json()

  verdict = result["verdict"]
  print(verdict["action"])          # proceed | fix_first | reject

  if verdict["action"] == "reject":
      # No transformation helps. Do not spend anything else on this clip.
      for reason in verdict["reasons"]:
          print(f"  {reason['check']}: {reason['detail']}")

  elif verdict["action"] == "fix_first":
      # Something is wrong and can be fixed. The price is already here.
      print(f"  fixable for {result['fix_quote']['credits']} credits")
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = "fd_live_your_key_here";
  const BASE_URL = "https://api.fastdrop.io/api/v1";

  const response = await fetch(`${BASE_URL}/readiness`, {
    method: "POST",
    headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      video_url: "https://example.com/take_04.mp4",
      context: "motion_analysis",
    }),
  });

  const { verdict, fix_quote } = await response.json();

  if (verdict.action === "reject") {
    // Nothing helps. Stop here rather than paying to find out again.
    verdict.reasons.forEach((r) => console.log(`  ${r.check}: ${r.detail}`));
  } else if (verdict.action === "fix_first") {
    console.log(`  fixable for ${fix_quote.credits} credits`);
  }
  ```
</CodeGroup>

<Tip>
  `GET /api/v1/samples` lists two videos that are broken on purpose, and checking
  or fixing those costs no credits. Point the examples on this page at one of them
  to watch the whole flow work before spending anything.
</Tip>

## Fix it, and check the fix took

Returns a job, because a transcode takes tens of seconds and an unverified
answer is not worth having early.

<CodeGroup>
  ```python Python theme={null}
  import time

  import httpx

  API_KEY = "fd_live_your_key_here"
  BASE_URL = "https://api.fastdrop.io/api/v1"
  HEADERS = {"X-API-Key": API_KEY}

  submit = httpx.post(
      f"{BASE_URL}/fix",
      headers={**HEADERS, "Idempotency-Key": "take-04-fix-1"},
      json={
          "video_url": "https://example.com/take_04.mp4",
          "context": "motion_analysis",
      },
      timeout=30,
  )
  submit.raise_for_status()
  job = submit.json()
  print(f"quoted {job['quote']['credits']} credits")

  # Poll until terminal. Every failure state has charged nothing.
  while True:
      time.sleep(10)
      status = httpx.get(
          f"{BASE_URL}/fix/{job['fix_job_id']}", headers=HEADERS, timeout=30
      ).json()
      if status["terminal"]:
          break

  if status["status"] == "succeeded":
      print(status["result_url"])
  else:
      # verification_failed means a file came back and we would not stand behind
      # it. The report says which check failed and what was measured.
      print(status["status"], status["error"])
      for check in status["verification"]["checks"]:
          if not check["passed"]:
              print(f"  {check['name']}: {check['detail']}")
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = "fd_live_your_key_here";
  const BASE_URL = "https://api.fastdrop.io/api/v1";
  const headers = { "X-API-Key": API_KEY, "Content-Type": "application/json" };

  const submit = await fetch(`${BASE_URL}/fix`, {
    method: "POST",
    // Makes a retry safe: the same key returns the original job rather than
    // charging and transcoding twice.
    headers: { ...headers, "Idempotency-Key": "take-04-fix-1" },
    body: JSON.stringify({
      video_url: "https://example.com/take_04.mp4",
      context: "motion_analysis",
    }),
  });

  const job = await submit.json();

  let status;
  do {
    await new Promise((r) => setTimeout(r, 10000));
    const poll = await fetch(`${BASE_URL}/fix/${job.fix_job_id}`, {
      headers: { "X-API-Key": API_KEY },
    });
    status = await poll.json();
  } while (!status.terminal);

  if (status.status === "succeeded") {
    console.log(status.result_url);
  } else {
    status.verification?.checks
      .filter((c) => !c.passed)
      .forEach((c) => console.log(`  ${c.name}: ${c.detail}`));
  }
  ```
</CodeGroup>

<Note>
  **Send an `Idempotency-Key`.** Retries are the normal case on a network call that
  takes a minute, and without one a retry charges and transcodes a second time.
  With one, it returns the original job and does not even re-read the video.
</Note>

## Classify a single video

<CodeGroup>
  ```python Python theme={null}
  import httpx

  API_KEY = "fd_live_your_key_here"
  BASE_URL = "https://api.fastdrop.io/api/v1"
  HEADERS = {"X-API-Key": API_KEY}

  WAIT_SECONDS = 60          # server holds the connection this long (max 90)
  CLIENT_TIMEOUT = 75        # must exceed WAIT_SECONDS
  MAX_ATTEMPTS = 10          # ~10 minutes at 60s per wait


  def classify_video(video_url: str, tier: str = "basic") -> dict:
      """Classify a video and wait for results."""

      # Submit job
      resp = httpx.post(
          f"{BASE_URL}/classify",
          headers=HEADERS,
          json={
              "video_url": video_url,
              "capabilities": ["classify", "thumbnails", "diagnostics"],
              "processing_tier": tier,
          },
      )
      resp.raise_for_status()
      job = resp.json()
      job_id = job["job_id"]
      print(f"Job submitted: {job_id} ({job['credits_charged']} credits)")

      # Wait for results. Each request returns the moment the job finishes,
      # so there is no sleep here — the wait is the sleep.
      for _ in range(MAX_ATTEMPTS):
          resp = httpx.get(
              f"{BASE_URL}/classify/{job_id}",
              params={"wait": WAIT_SECONDS},
              headers=HEADERS,
              timeout=CLIENT_TIMEOUT,
          )
          resp.raise_for_status()
          status = resp.json()

          if status["status"] == "completed":
              return status["results"]
          if status["status"] == "failed":
              raise Exception(f"Job failed: {status['error']}")

          print(f"  Status: {status['status']} ({status['progress']}%)")

      raise TimeoutError(f"Job {job_id} did not finish in time")


  # Usage
  results = classify_video("https://example.com/my-video.mp4")
  print(f"Role: {results['role']} ({results['role_confidence']:.0%})")
  print(f"Filename: {results['suggested_filename']}")
  print(f"Folder: {results['suggested_folder']}")
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = "fd_live_your_key_here";
  const BASE_URL = "https://api.fastdrop.io/api/v1";

  const WAIT_SECONDS = 60;      // server holds the connection this long (max 90)
  const CLIENT_TIMEOUT = 75_000; // must exceed WAIT_SECONDS
  const MAX_ATTEMPTS = 10;       // ~10 minutes at 60s per wait

  async function classifyVideo(videoUrl, tier = "basic") {
    // Submit job
    const submitResp = await fetch(`${BASE_URL}/classify`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-API-Key": API_KEY,
      },
      body: JSON.stringify({
        video_url: videoUrl,
        capabilities: ["classify", "thumbnails", "diagnostics"],
        processing_tier: tier,
      }),
    });

    if (!submitResp.ok) throw new Error(await submitResp.text());
    const job = await submitResp.json();
    console.log(`Job submitted: ${job.job_id} (${job.credits_charged} credits)`);

    // Wait for results. Each request returns the moment the job finishes,
    // so there is no sleep here — the wait is the sleep.
    for (let i = 0; i < MAX_ATTEMPTS; i++) {
      const statusResp = await fetch(
        `${BASE_URL}/classify/${job.job_id}?wait=${WAIT_SECONDS}`,
        {
          headers: { "X-API-Key": API_KEY },
          signal: AbortSignal.timeout(CLIENT_TIMEOUT),
        }
      );
      const status = await statusResp.json();

      if (status.status === "completed") return status.results;
      if (status.status === "failed") throw new Error(status.error.message);

      console.log(`  Status: ${status.status} (${status.progress}%)`);
    }

    throw new Error(`Job ${job.job_id} did not finish in time`);
  }

  // Usage
  const results = await classifyVideo("https://example.com/my-video.mp4");
  console.log(`Role: ${results.role} (${Math.round(results.role_confidence * 100)}%)`);
  console.log(`Filename: ${results.suggested_filename}`);
  console.log(`Folder: ${results.suggested_folder}`);
  ```
</CodeGroup>

## Batch classification

<CodeGroup>
  ```python Python theme={null}
  import httpx

  API_KEY = "fd_live_your_key_here"
  BASE_URL = "https://api.fastdrop.io/api/v1"
  HEADERS = {"X-API-Key": API_KEY}

  WAIT_SECONDS = 90            # the maximum; batches are the slowest thing here
  CLIENT_TIMEOUT = 105
  MAX_ATTEMPTS = 20            # ~30 minutes


  def classify_batch(video_urls: list[str], tier: str = "basic") -> list[dict]:
      """Submit a batch of videos and wait for all results."""

      # Submit batch
      videos = [
          {"video_url": url, "capabilities": ["classify", "thumbnails", "diagnostics"]}
          for url in video_urls
      ]
      resp = httpx.post(
          f"{BASE_URL}/batch",
          headers=HEADERS,
          json={"videos": videos, "processing_tier": tier},
      )
      resp.raise_for_status()
      batch = resp.json()
      batch_id = batch["batch_id"]
      print(f"Batch submitted: {batch_id} ({batch['total_credits']} credits, {batch['total_videos']} videos)")

      # Wait for results. Batch status is recomputed from every job row on each
      # request, so this is the endpoint you least want to poll blindly.
      for _ in range(MAX_ATTEMPTS):
          resp = httpx.get(
              f"{BASE_URL}/batch/{batch_id}",
              params={"wait": WAIT_SECONDS},
              headers=HEADERS,
              timeout=CLIENT_TIMEOUT,
          )
          resp.raise_for_status()
          status = resp.json()

          done = status["completed_videos"] + status["failed_videos"]
          print(f"  Progress: {done}/{status['total_videos']} ({status['status']})")

          if status["status"] in ("completed", "partial", "failed"):
              return status["jobs"]

      raise TimeoutError(f"Batch {batch_id} did not finish in time")


  # Usage
  urls = [
      "https://example.com/video1.mp4",
      "https://example.com/video2.mp4",
      "https://example.com/video3.mp4",
  ]
  jobs = classify_batch(urls)
  for job in jobs:
      if job["status"] == "completed":
          r = job["results"]
          print(f"{job['video_url']} -> {r['role']} ({r['role_confidence']:.0%})")
      else:
          print(f"{job['video_url']} -> FAILED: {job.get('error', 'unknown')}")
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = "fd_live_your_key_here";
  const BASE_URL = "https://api.fastdrop.io/api/v1";

  async function classifyBatch(videoUrls, tier = "basic") {
    const videos = videoUrls.map((url) => ({
      video_url: url,
      capabilities: ["classify", "thumbnails", "diagnostics"],
    }));

    const submitResp = await fetch(`${BASE_URL}/batch`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-API-Key": API_KEY,
      },
      body: JSON.stringify({ videos, processing_tier: tier }),
    });

    if (!submitResp.ok) throw new Error(await submitResp.text());
    const batch = await submitResp.json();
    console.log(`Batch: ${batch.batch_id} (${batch.total_credits} credits)`);

    const WAIT_SECONDS = 90;       // the maximum; batches are the slowest thing here
    const CLIENT_TIMEOUT = 105_000;
    const MAX_ATTEMPTS = 20;       // ~30 minutes

    for (let i = 0; i < MAX_ATTEMPTS; i++) {
      const resp = await fetch(
        `${BASE_URL}/batch/${batch.batch_id}?wait=${WAIT_SECONDS}`,
        {
          headers: { "X-API-Key": API_KEY },
          signal: AbortSignal.timeout(CLIENT_TIMEOUT),
        }
      );
      const status = await resp.json();

      const done = status.completed_videos + status.failed_videos;
      console.log(`  Progress: ${done}/${status.total_videos}`);

      if (["completed", "partial", "failed"].includes(status.status)) {
        return status.jobs;
      }
    }

    throw new Error(`Batch ${batch.batch_id} did not finish in time`);
  }
  ```
</CodeGroup>

## Webhook listener

<CodeGroup>
  ```python Python (Flask) theme={null}
  import hmac
  import hashlib
  import json
  from flask import Flask, request, jsonify

  app = Flask(__name__)
  WEBHOOK_SECRET = "your_webhook_secret_here"

  seen_events: set[str] = set()   # use Redis or a table in production


  @app.route("/webhooks/fastdrop", methods=["POST"])
  def handle_webhook():
      # Verify signature against the RAW body. Re-serialising the parsed JSON
      # will not reproduce our bytes and the signature will not match.
      signature = request.headers.get("X-FastDrop-Signature", "")
      expected = hmac.new(
          WEBHOOK_SECRET.encode("utf-8"),
          request.data,
          hashlib.sha256,
      ).hexdigest()
      received = signature.replace("sha256=", "")

      if not hmac.compare_digest(expected, received):
          return jsonify({"error": "Invalid signature"}), 401

      payload = request.json
      event = payload["event"]
      data = payload["data"]

      # Delivery is at-least-once: retries and manual replays reuse the same id.
      if payload["id"] in seen_events:
          return jsonify({"received": True, "duplicate": True}), 200
      seen_events.add(payload["id"])

      if event == "job.completed":
          print(f"Job {data['job_id']} completed: {data['results']['role']}")
      elif event == "job.failed":
          print(f"Job {data['job_id']} failed")
      elif event == "batch.completed":
          print(f"Batch completed with {len(data.get('jobs', []))} videos")

      # Answer within 15s. Anything slower counts as a failed delivery and is
      # retried — queue slow work instead of doing it here.
      return jsonify({"received": True}), 200
  ```

  ```javascript JavaScript (Express) theme={null}
  const express = require("express");
  const crypto = require("crypto");

  const app = express();
  const WEBHOOK_SECRET = "your_webhook_secret_here";

  const seen = new Set(); // use Redis in production

  // express.raw so the body stays byte-identical and verifiable.
  app.post("/webhooks/fastdrop", express.raw({ type: "application/json" }), (req, res) => {
    const signature = req.headers["x-fastdrop-signature"] || "";
    const expected = crypto
      .createHmac("sha256", WEBHOOK_SECRET)
      .update(req.body)
      .digest("hex");
    const received = signature.replace("sha256=", "");

    if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const payload = JSON.parse(req.body);
    const { id, event, data } = payload;

    // Delivery is at-least-once: retries and manual replays reuse the same id.
    if (seen.has(id)) return res.json({ received: true, duplicate: true });
    seen.add(id);

    if (event === "job.completed") {
      console.log(`Job ${data.job_id} completed: ${data.results.role}`);
    } else if (event === "job.failed") {
      console.log(`Job ${data.job_id} failed`);
    }

    // Answer within 15s. Anything slower counts as a failed delivery and is
    // retried — queue slow work instead of doing it here.
    res.json({ received: true });
  });

  app.listen(3000);
  ```
</CodeGroup>

## Transcribe and translate a video

<CodeGroup>
  ```python Python theme={null}
  import httpx

  API_KEY = "fd_live_your_key_here"
  BASE_URL = "https://api.fastdrop.io/api/v1"
  HEADERS = {"X-API-Key": API_KEY}

  WAIT_SECONDS = 90            # transcription runs 45s+; use a long wait
  CLIENT_TIMEOUT = 105
  MAX_ATTEMPTS = 20


  def transcribe_video(video_url: str, output_formats: list[str] | None = None) -> dict:
      """Transcribe a video with translation and optional subtitle files."""

      body = {"video_url": video_url}
      if output_formats:
          body["output_formats"] = output_formats

      # Submit job
      resp = httpx.post(f"{BASE_URL}/transcribe", headers=HEADERS, json=body)
      resp.raise_for_status()
      job = resp.json()
      job_id = job["job_id"]
      print(f"Job submitted: {job_id} ({job['credits_charged']} credits)")

      # Wait for results
      for _ in range(MAX_ATTEMPTS):
          resp = httpx.get(
              f"{BASE_URL}/classify/{job_id}",
              params={"wait": WAIT_SECONDS},
              headers=HEADERS,
              timeout=CLIENT_TIMEOUT,
          )
          resp.raise_for_status()
          status = resp.json()

          if status["status"] == "completed":
              return status["results"]["transcription"]
          if status["status"] == "failed":
              raise Exception(f"Job failed: {status['error']}")

          print(f"  Status: {status['status']} ({status['progress']}%)")

      raise TimeoutError(f"Job {job_id} did not finish in time")


  # JSON only
  result = transcribe_video("https://example.com/interview-arabic.mp4")
  print(f"Language: {result['language_name']}")
  print(f"Transcript: {result['text'][:100]}...")
  if "translation" in result:
      print(f"English: {result['translation']['text'][:100]}...")

  # With subtitle files
  result = transcribe_video(
      "https://example.com/interview-arabic.mp4",
      output_formats=["srt", "txt"],
  )
  if "files" in result:
      print(f"Source SRT: {result['files']['source_srt']}")
      print(f"English SRT: {result['files']['english_srt']}")
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = "fd_live_your_key_here";
  const BASE_URL = "https://api.fastdrop.io/api/v1";

  async function transcribeVideo(videoUrl, outputFormats = null) {
    const body = { video_url: videoUrl };
    if (outputFormats) body.output_formats = outputFormats;

    // Submit job
    const submitResp = await fetch(`${BASE_URL}/transcribe`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-API-Key": API_KEY,
      },
      body: JSON.stringify(body),
    });

    if (!submitResp.ok) throw new Error(await submitResp.text());
    const job = await submitResp.json();
    console.log(`Job submitted: ${job.job_id} (${job.credits_charged} credits)`);

    const WAIT_SECONDS = 90;       // transcription runs 45s+; use a long wait
    const CLIENT_TIMEOUT = 105_000;
    const MAX_ATTEMPTS = 20;

    for (let i = 0; i < MAX_ATTEMPTS; i++) {
      const statusResp = await fetch(
        `${BASE_URL}/classify/${job.job_id}?wait=${WAIT_SECONDS}`,
        {
          headers: { "X-API-Key": API_KEY },
          signal: AbortSignal.timeout(CLIENT_TIMEOUT),
        }
      );
      const status = await statusResp.json();

      if (status.status === "completed") return status.results.transcription;
      if (status.status === "failed") throw new Error(status.error.message);

      console.log(`  Status: ${status.status} (${status.progress}%)`);
    }

    throw new Error(`Job ${job.job_id} did not finish in time`);
  }

  // JSON only
  const result = await transcribeVideo("https://example.com/interview-arabic.mp4");
  console.log(`Language: ${result.language_name}`);
  console.log(`Transcript: ${result.text.slice(0, 100)}...`);
  if (result.translation) {
    console.log(`English: ${result.translation.text.slice(0, 100)}...`);
  }

  // With subtitle files
  const withFiles = await transcribeVideo(
    "https://example.com/interview-arabic.mp4",
    ["srt", "txt"]
  );
  if (withFiles.files) {
    console.log(`Source SRT: ${withFiles.files.source_srt}`);
    console.log(`English SRT: ${withFiles.files.english_srt}`);
  }
  ```
</CodeGroup>

## Check credit balance

<CodeGroup>
  ```python Python theme={null}
  import httpx

  resp = httpx.get(
      "https://api.fastdrop.io/api/v1/usage",
      headers={"X-API-Key": "fd_live_your_key_here"},
  )
  usage = resp.json()

  print(f"Plan: {usage['plan_tier']}")
  print(f"Credits: {usage['credits_remaining']}/{usage['credits_monthly']}")
  print(f"Used this month: {usage['usage_this_month']['total_credits']}")
  ```

  ```javascript JavaScript theme={null}
  const resp = await fetch("https://api.fastdrop.io/api/v1/usage", {
    headers: { "X-API-Key": "fd_live_your_key_here" },
  });
  const usage = await resp.json();

  console.log(`Plan: ${usage.plan_tier}`);
  console.log(`Credits: ${usage.credits_remaining}/${usage.credits_monthly}`);
  ```
</CodeGroup>

## Error handling

<CodeGroup>
  ```python Python theme={null}
  import httpx

  def safe_classify(video_url: str, api_key: str) -> dict | None:
      """Classify with proper error handling."""
      resp = httpx.post(
          "https://api.fastdrop.io/api/v1/classify",
          headers={"X-API-Key": api_key},
          json={"video_url": video_url},
      )

      if resp.status_code == 401:
          print("Invalid API key")
          return None
      elif resp.status_code == 402:
          error = resp.json()["error"]
          print(f"Insufficient credits: need {error['details']['credits_required']}, "
                f"have {error['details']['credits_available']}")
          return None
      elif resp.status_code == 429:
          retry_after = resp.json()["error"]["details"]["retry_after_seconds"]
          print(f"Rate limited. Retry after {retry_after}s")
          return None

      resp.raise_for_status()
      return resp.json()
  ```

  ```javascript JavaScript theme={null}
  async function safeClassify(videoUrl, apiKey) {
    const resp = await fetch("https://api.fastdrop.io/api/v1/classify", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-API-Key": apiKey,
      },
      body: JSON.stringify({ video_url: videoUrl }),
    });

    if (resp.status === 401) {
      console.error("Invalid API key");
      return null;
    }
    if (resp.status === 402) {
      const { error } = await resp.json();
      console.error(`Need ${error.details.credits_required} credits, have ${error.details.credits_available}`);
      return null;
    }
    if (resp.status === 429) {
      const { error } = await resp.json();
      console.error(`Rate limited. Retry after ${error.details.retry_after_seconds}s`);
      return null;
    }

    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
    return await resp.json();
  }
  ```
</CodeGroup>


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