> ## 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.

# Getting your results

> Webhooks, long-poll, and when to use which

Most FastDrop capabilities are asynchronous: you submit a video, we return a `job_id`, and the work happens on a worker. There are three ways to get the result, and the right one depends on how long the answer takes and whether your service can receive an HTTP request.

| Your situation | Use | Requests per job |
| - | - | - |
| You need a decision *before* committing to work | [`POST /v1/readiness`](/api-reference/check-readiness) — answers inline | 1 |
| Your service can receive HTTP callbacks | **Webhooks** | 0 |
| It cannot (CLI, notebook, browser, agent, firewalled) | **Long-poll** (`?wait=N`) | 1–2 |
| Reconciling, auditing, or recovering | Plain polling | as needed |

<Note>
  Polling is never removed and never deprecated. Even with webhooks configured, the status endpoint stays the source of truth — it is how you reconcile after an outage on either side. What changed is that it is no longer the *default*.
</Note>

## Webhooks — the default

Register an endpoint once and stop asking:

```bash theme={null}
curl -X POST https://api.fastdrop.io/api/v1/webhooks \
  -H "Content-Type: application/json" \
  -H "X-API-Key: fd_live_your_key_here" \
  -d '{
    "url": "https://your-server.com/webhooks/fastdrop",
    "events": ["job.completed", "job.failed", "batch.completed", "batch.failed"]
  }'
```

You get the result the moment it exists, you make zero status requests, and you receive batch events that no single job status call could tell you about. See the [Webhooks guide](/guides/webhooks) for payload shape, signature verification, and delivery history.

## Long-poll — when you cannot receive callbacks

Add `?wait=N` to any status request. The connection is held open until the job reaches a terminal state, or `N` seconds elapse — whichever comes first.

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.fastdrop.io/api/v1/classify/JOB_ID?wait=60" \
    -H "X-API-Key: fd_live_your_key_here"
  ```

  ```python Python theme={null}
  import httpx

  # One request. Returns as soon as the job finishes.
  response = httpx.get(
      f"https://api.fastdrop.io/api/v1/classify/{job_id}",
      params={"wait": 60},
      headers={"X-API-Key": "fd_live_your_key_here"},
      timeout=75,  # must exceed `wait`
  )
  data = response.json()
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    `https://api.fastdrop.io/api/v1/classify/${jobId}?wait=60`,
    { headers: { "X-API-Key": "fd_live_your_key_here" } }
  );
  const data = await response.json();
  ```
</CodeGroup>

**Set your client timeout above `wait`.** A 60-second wait with a 30-second client timeout hangs up on a request that was about to answer.

`wait` accepts 0–90 seconds and is available on:

* `GET /v1/classify/{job_id}`
* `GET /v1/batch/{batch_id}`
* `GET /mpp/classify/{job_id}`

A wait that elapses is **not an error**. You get the current state — the same response `wait=0` would have returned — and can call again:

```python theme={null}
for _ in range(MAX_ATTEMPTS):
    data = httpx.get(url, params={"wait": 90}, headers=headers, timeout=105).json()
    if data["status"] in ("completed", "failed"):
        break
    # Still running. Loop immediately — the wait already did the waiting.
```

Note there is no `sleep` in that loop. The wait *is* the sleep, and it ends the instant the job does.

## Why this matters more than it looks

A 30–180 second job polled every 5 seconds costs **6–36 requests** to return one answer. Nearly all of them say "not yet".

That has three consequences:

* **It can rate-limit you on a single job.** The Free tier allows 10 requests/minute. A 5-second poll loop issues 12 in the first minute.
* **For agents, every poll is a turn.** An MCP or machine-payments client spends a model turn and a round of tokens on each check. One waiting request costs one turn for the answer, not one turn per guess.
* **It scales badly.** Batch status is recomputed from every job row on each request. A 50-video batch polled every 3 seconds does that work hundreds of times to return the same answer.

## Choosing a `wait` value

| Capability | Typical duration | Suggested `wait` |
| - | - | - |
| `thumbnails`, `diagnostics`, `duplicates` | \~10s | 15 |
| `classify` (basic → premium) | 30–180s | 60–90 |
| `clips` | \~20s | 30 |
| `transcribe` | 45s+ | 90 |
| Batch (50 videos) | several minutes | 90, then loop |

## Idempotency

Whichever mode you use, deliveries are **at-least-once**. Webhook events carry an `id` (`evt_...`) that is stable across retries and manual replays — key your handler on it and ignore ids you have already processed.

## Reconciliation

Webhooks are a delivery mechanism, not a system of record. If your endpoint was down past the retry window, or you are not certain you processed everything:

1. Check [delivery history](/api-reference/list-deliveries) — `GET /v1/webhooks/deliveries?status=failed` shows what we could not deliver and why.
2. [Replay](/api-reference/retry-delivery) what you missed. The payload and its `id` are unchanged, so it costs no credits and your handler dedupes normally.
3. Or read the job directly with `GET /v1/classify/{job_id}` — always available, always authoritative.

<Card title="Webhooks guide" icon="bell" href="/guides/webhooks">
  Payload shape, signature verification, delivery history, and replay.
</Card>


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