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

# PDF to Video Example

> Turn reports, papers and study notes in PDF into narrated videos

## Overview

PDF to Video extracts the text of a PDF, turns it into a narration split into scenes, reads it with a voice and puts a visual under each scene. [PDF to Brainrot](/api-reference/video/pdf-to-brainrot) does the same over a gameplay clip.

Send the PDF as a public `https://` link in `pdfUrl` (up to 20 MB; Hooked downloads it) or as the file itself in `pdfBase64` (up to 10 MB). The PDF needs a text layer: a scanned or image-only PDF answers `400` before anything is charged. `summaryType`, `targetDuration` and `customPrompt` work as in [Article to Video](/examples/article-to-video).

<Note>
  The create call downloads the PDF and checks its text, then answers in a few seconds with the `projectId` and status `processing`. The scenes are written after that, then the video is generated and rendered. If the scenes cannot be written, the project becomes `failed` (the reason is in `message`), the credits come back and your webhook gets the failure.
</Note>

## From a link

```javascript theme={null}
const response = await fetch('https://api.hooked.so/v1/project/create/pdf-to-video', {
  method: 'POST',
  headers: {
    'x-api-key': process.env.HOOKED_API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    pdfUrl: 'https://example.com/reports/annual-report-2025.pdf',
    voiceId: '1000',
    summaryType: 'summarize',
    targetDuration: 60,
    customPrompt: 'Lead with the three biggest numbers',
    mediaType: 'ai-images',
    presetSettings: { preset: 'cinematic' },
    aspectRatio: 'ratio_9_16',
    caption: { preset: 'tiktok' },
    webhook: 'https://yoursite.com/hooked-webhook?token=YOUR_SECRET',
    metadata: { reportId: '2025-annual' }
  })
});

const { data } = await response.json();
console.log('Project ID:', data.projectId); // status: "processing"
```

## From a file on disk

```javascript theme={null}
import { readFileSync } from 'node:fs';

const body = {
  pdfBase64: readFileSync('./employee-handbook.pdf').toString('base64'),
  pdfFileName: 'employee-handbook.pdf',
  voiceId: '1000',
  summaryType: 'key_as_is',
  mediaType: 'ai-images',
  webhook: 'https://yoursite.com/hooked-webhook?token=YOUR_SECRET'
};

const response = await fetch('https://api.hooked.so/v1/project/create/pdf-to-video', {
  method: 'POST',
  headers: {
    'x-api-key': process.env.HOOKED_API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(body)
});
```

Files over 10 MB go through `pdfUrl`: put the file somewhere public (a signed link of your own storage works) and send the link.

## Study notes over gameplay

```python theme={null}
import os
import requests

response = requests.post(
    "https://api.hooked.so/v1/project/create/pdf-to-brainrot",
    headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
    json={
        "pdfUrl": "https://example.com/notes/biology-chapter-3.pdf",
        "voiceId": "1000",
        "summaryType": "summarize",
        "targetDuration": 60,
        "gameplaySettings": {"selectedGame": "subway-s", "selectedVideo": "subway-s-3"},
        "caption": {"preset": "beast"},
        "webhook": "https://yoursite.com/hooked-webhook?token=YOUR_SECRET",
    },
)

print(response.json()["data"]["projectId"])
```

## When the PDF is refused

| Answer | What to do |
| - | - |
| `400 pdf: The PDF has no readable text …` | It is a scan or an image-only PDF: export it with OCR (a text layer) first. |
| `400 pdf: The PDF is password-protected …` | Send a copy without a password. |
| `400 pdfUrl: The PDF is larger than 20 MB` / `pdfBase64: The PDF is larger than 10 MB …` | Split the document, or compress its images. |
| `400 pdfUrl: The file at this URL is not a PDF` | The link answers an HTML page (a viewer or a login) instead of the file: use the direct download link. |
| `400 pdfUrl: Must be a public https URL (…)` | The host is private, local or redirects to one. |

Nothing is charged for any of these.

## Getting the video

Wait for the webhook or poll [`GET /v1/project/{projectId}`](/api-reference/project/details) until `video.status` is `COMPLETED`, then download `video.url`.

<Warning>
  Send an [`Idempotency-Key`](/guides/idempotency) header to retry safely: if a request times out or answers 5xx, sending it again with the same key and body returns the first answer instead of creating (and charging) the project twice.
</Warning>

<Note>
  Teams in **bring-your-own-keys** mode are not charged credits, but need OpenRouter and ElevenLabs in Settings → AI keys (and Gemini for the Omni Flash model). No Firecrawl key is needed: Hooked reads the PDF itself. A missing key answers `402 missing_credentials` with the list in `missingProviders`, before anything is created.
</Note>

See the [PDF to Video](/api-reference/video/pdf-to-video) and [PDF to Brainrot](/api-reference/video/pdf-to-brainrot) references for every field and error.


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