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

# Lifecycle & Polling

> How a transcription job progresses and how to know when it's ready.

`POST /api/transcribeVideo` returns immediately with a `projectId` — the actual transcription happens asynchronously in the background and typically takes **between a few seconds and several minutes** depending on video length and queue load.

There are two ways to find out when a transcription is ready: **webhook** (push) or **polling** (pull). You can use either, or both.

## Project States

Every project moves through a small set of states. The current value is returned in the `state` field of `GET /api/detail/{id}`.

| State           | Meaning                                                                 | Action                                                |
| --------------- | ----------------------------------------------------------------------- | ----------------------------------------------------- |
| `upload`        | The video is being downloaded from YouTube and uploaded to our storage. | Wait.                                                 |
| `transcription` | The audio is being transcribed by our speech engine.                    | Wait.                                                 |
| `done`          | Transcription finished, full text and transcript JSON available.        | Read the result.                                      |
| `error`         | Something failed in the pipeline.                                       | Retry (re-submit), or contact support if it persists. |

A typical run looks like: `upload → transcription → done`. Most videos under 10 minutes complete in **30–90 seconds** total.

## Pattern 1: Webhook (recommended)

Provide a `webhookUrl` when you call `POST /api/transcribeVideo`:

```json theme={null}
{
  "youtubeVideoId": "dQw4w9WgXcQ",
  "language": "en",
  "webhookUrl": "https://yourapp.com/hooks/transcribetube"
}
```

When the project reaches `done`, we POST the full payload to your URL. See [Webhooks](/api-documentation/webhooks) for the payload shape, headers, and the (lack of) retry policy.

## Pattern 2: Polling

If you don't have a public webhook endpoint, poll `GET /api/detail/{id}` periodically until the `state` is `done`.

```js theme={null}
async function waitForTranscription(projectId, apiKey, { intervalMs = 5000, maxMs = 600000 } = {}) {
  const deadline = Date.now() + maxMs;
  while (Date.now() < deadline) {
    const res = await fetch(`https://api.transcribetube.com/api/detail/${projectId}`, {
      headers: { "api-key": apiKey },
    });
    const project = await res.json();
    if (project.state === "done") return project;
    if (project.state === "error") throw new Error(`Project ${projectId} failed`);
    await new Promise((r) => setTimeout(r, intervalMs));
  }
  throw new Error(`Timed out waiting for project ${projectId}`);
}
```

**Recommended cadence**:

* Poll every **5–10 seconds** for the first 2 minutes (short videos finish fast).
* Then back off to **30–60 seconds** for the next 10 minutes.
* Give up after \~10 minutes and report a failure — the project will likely be in `error` state by then.

Polling more frequently than once every 3 seconds wastes both your quota and our rate limits without making the job complete any sooner.

## Pattern 3: Both (most robust)

The webhook is single-attempt and not retried (see [Webhooks](/api-documentation/webhooks)). To make sure you never miss a result:

1. Submit with a `webhookUrl`.
2. Have your webhook handler mark the project as "received" in your own database.
3. Run a background job every few minutes that lists pending projects via `GET /api/list` and reconciles any that didn't get a webhook by calling `GET /api/detail/{id}`.

## Response Shape by State

Note that `GET /api/detail/{id}` returns **different fields depending on state**:

<CodeGroup>
  ```json done theme={null}
  {
    "id": "f3c1e7a0-2b48-4a91-8a30-9b1c2d3e4f50",
    "name": "Rick Astley - Never Gonna Give You Up",
    "youtubeId": "dQw4w9WgXcQ",
    "language": "en",
    "text": "We're no strangers to love\n\nYou know the rules and so do I\n\n..."
  }
  ```

  ```json transcription / upload theme={null}
  {
    "id": "f3c1e7a0-2b48-4a91-8a30-9b1c2d3e4f50",
    "name": "Rick Astley - Never Gonna Give You Up",
    "state": "transcription",
    "message": "Transcription is in progress",
    "videoLength": 213,
    "createdAt": "2026-01-15T14:30:00.000Z"
  }
  ```
</CodeGroup>

Detect "ready" by checking `state === "done"` **or** by the presence of the `text` field.
