curl -X POST "https://api.hooked.so/v1/project/create/pdf-to-brainrot" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"pdfUrl": "https://example.com/notes/biology-chapter-3.pdf",
"voiceId": "1004",
"summaryType": "summarize",
"targetDuration": 60,
"customPrompt": "Explain it like a fun fact countdown",
"gameplaySettings": { "selectedGame": "subway-s", "selectedVideo": "subway-s-3" },
"caption": { "preset": "beast" },
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET"
}'
const response = await fetch("https://api.hooked.so/v1/project/create/pdf-to-brainrot", {
method: "POST",
headers: {
"x-api-key": "your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
pdfUrl: "https://example.com/notes/biology-chapter-3.pdf",
voiceId: "1004",
summaryType: "summarize",
targetDuration: 60,
customPrompt: "Explain it like a fun fact countdown",
gameplaySettings: { selectedGame: "subway-s", selectedVideo: "subway-s-3" },
caption: { preset: "beast" },
webhook: "https://example.com/hooked/webhook?token=YOUR_SECRET",
}),
});
const { data } = await response.json();
console.log(data.projectId);
import requests
response = requests.post(
"https://api.hooked.so/v1/project/create/pdf-to-brainrot",
headers={"x-api-key": "your_api_key_here"},
json={
"pdfUrl": "https://example.com/notes/biology-chapter-3.pdf",
"voiceId": "1004",
"summaryType": "summarize",
"targetDuration": 60,
"customPrompt": "Explain it like a fun fact countdown",
"gameplaySettings": {"selectedGame": "subway-s", "selectedVideo": "subway-s-3"},
"caption": {"preset": "beast"},
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET",
},
)
print(response.json()["data"]["projectId"])
{
"success": true,
"data": {
"projectId": "cm4x9k2p10001ab12cd34ef56",
"status": "processing"
},
"message": "PDF to Brainrot successfully created"
}
{
"success": false,
"message": "gameplaySettings.selectedGame: Invalid game \"tetris\". Allowed values are: minecraft, subway-s, temple-run, gta, fortnite, roblox, free-fire, custom"
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"code": "missing_credentials",
"message": "Your team uses its own provider keys (BYOK) and these are missing or invalid: OpenRouter, ElevenLabs. Add them in Settings → AI keys.",
"errorCode": "MISSING_CREDENTIALS",
"missingProviders": ["openrouter", "elevenlabs"],
"details": {}
}
{
"success": false,
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Not enough credits. Needed: 30, Available: 12",
"creditsNeeded": 30,
"creditsAvailable": 12
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
PDF to Brainrot
Turn a PDF into a narrated video over gameplay footage
curl -X POST "https://api.hooked.so/v1/project/create/pdf-to-brainrot" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"pdfUrl": "https://example.com/notes/biology-chapter-3.pdf",
"voiceId": "1004",
"summaryType": "summarize",
"targetDuration": 60,
"customPrompt": "Explain it like a fun fact countdown",
"gameplaySettings": { "selectedGame": "subway-s", "selectedVideo": "subway-s-3" },
"caption": { "preset": "beast" },
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET"
}'
const response = await fetch("https://api.hooked.so/v1/project/create/pdf-to-brainrot", {
method: "POST",
headers: {
"x-api-key": "your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
pdfUrl: "https://example.com/notes/biology-chapter-3.pdf",
voiceId: "1004",
summaryType: "summarize",
targetDuration: 60,
customPrompt: "Explain it like a fun fact countdown",
gameplaySettings: { selectedGame: "subway-s", selectedVideo: "subway-s-3" },
caption: { preset: "beast" },
webhook: "https://example.com/hooked/webhook?token=YOUR_SECRET",
}),
});
const { data } = await response.json();
console.log(data.projectId);
import requests
response = requests.post(
"https://api.hooked.so/v1/project/create/pdf-to-brainrot",
headers={"x-api-key": "your_api_key_here"},
json={
"pdfUrl": "https://example.com/notes/biology-chapter-3.pdf",
"voiceId": "1004",
"summaryType": "summarize",
"targetDuration": 60,
"customPrompt": "Explain it like a fun fact countdown",
"gameplaySettings": {"selectedGame": "subway-s", "selectedVideo": "subway-s-3"},
"caption": {"preset": "beast"},
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET",
},
)
print(response.json()["data"]["projectId"])
{
"success": true,
"data": {
"projectId": "cm4x9k2p10001ab12cd34ef56",
"status": "processing"
},
"message": "PDF to Brainrot successfully created"
}
{
"success": false,
"message": "gameplaySettings.selectedGame: Invalid game \"tetris\". Allowed values are: minecraft, subway-s, temple-run, gta, fortnite, roblox, free-fire, custom"
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"code": "missing_credentials",
"message": "Your team uses its own provider keys (BYOK) and these are missing or invalid: OpenRouter, ElevenLabs. Add them in Settings → AI keys.",
"errorCode": "MISSING_CREDENTIALS",
"missingProviders": ["openrouter", "elevenlabs"],
"details": {}
}
{
"success": false,
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Not enough credits. Needed: 30, Available: 12",
"creditsNeeded": 30,
"creditsAvailable": 12
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
Overview
PDF to Brainrot is PDF to Video with a gameplay clip in the background: the text of the PDF becomes a narration, a voice reads it and the captions run over Minecraft parkour, Subway Surfers style runs and other gameplay. It is the format for study notes, summaries and explainers that hold attention on TikTok and Shorts.- The PDF:
pdfUrl(a publichttps://link, up to 20 MB, downloaded by Hooked) orpdfBase64(the file base64-encoded, up to 10 MB), not both. It needs a text layer: a scanned or image-only PDF answers400before anything is charged. See Sending the PDF. - The narration:
summaryType(summarizeby default,summarize_longorkey_as_is),targetDuration(default 30 seconds, not used withkey_as_is) andcustomPrompt(withsummarizeonly). - The background:
gameplaySettings(defaultminecraft/minecraft-1), orselectedGame: "custom"with a video of your library asselectedVideo. There is nomediaType: the background is always gameplay.
projectId and status processing: the PDF is downloaded and its text checked in the request, and splitting it into scenes and making the video happen after that. What can be refused up front (an invalid body, a link that is not a PDF, a PDF without a text layer, missing keys, too few credits) is still answered with a 400 or 402 and nothing is created. If the scenes cannot be written later, the project becomes failed with the reason in message, the credits come back and your webhook gets the failure.POST https://api.hooked.so/v1/project/create/pdf-to-brainrot
curl -X POST "https://api.hooked.so/v1/project/create/pdf-to-brainrot" \
-H "x-api-key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"pdfUrl": "https://example.com/notes/biology-chapter-3.pdf",
"voiceId": "1004",
"summaryType": "summarize",
"targetDuration": 60,
"customPrompt": "Explain it like a fun fact countdown",
"gameplaySettings": { "selectedGame": "subway-s", "selectedVideo": "subway-s-3" },
"caption": { "preset": "beast" },
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET"
}'
const response = await fetch("https://api.hooked.so/v1/project/create/pdf-to-brainrot", {
method: "POST",
headers: {
"x-api-key": "your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
pdfUrl: "https://example.com/notes/biology-chapter-3.pdf",
voiceId: "1004",
summaryType: "summarize",
targetDuration: 60,
customPrompt: "Explain it like a fun fact countdown",
gameplaySettings: { selectedGame: "subway-s", selectedVideo: "subway-s-3" },
caption: { preset: "beast" },
webhook: "https://example.com/hooked/webhook?token=YOUR_SECRET",
}),
});
const { data } = await response.json();
console.log(data.projectId);
import requests
response = requests.post(
"https://api.hooked.so/v1/project/create/pdf-to-brainrot",
headers={"x-api-key": "your_api_key_here"},
json={
"pdfUrl": "https://example.com/notes/biology-chapter-3.pdf",
"voiceId": "1004",
"summaryType": "summarize",
"targetDuration": 60,
"customPrompt": "Explain it like a fun fact countdown",
"gameplaySettings": {"selectedGame": "subway-s", "selectedVideo": "subway-s-3"},
"caption": {"preset": "beast"},
"webhook": "https://example.com/hooked/webhook?token=YOUR_SECRET",
},
)
print(response.json()["data"]["projectId"])
More request bodies
{
"pdfUrl": "https://example.com/notes/history-summary.pdf",
"voiceId": "1004",
"summaryType": "key_as_is",
"gameplaySettings": { "selectedGame": "custom", "selectedVideo": "cm4x9k2p10001ab12cd34ef56" }
}
{
"pdfUrl": "https://example.com/notes/chemistry-basics.pdf",
"voiceId": "1004",
"aspectRatio": "ratio_16_9"
}
{
"success": true,
"data": {
"projectId": "cm4x9k2p10001ab12cd34ef56",
"status": "processing"
},
"message": "PDF to Brainrot successfully created"
}
{
"success": false,
"message": "gameplaySettings.selectedGame: Invalid game \"tetris\". Allowed values are: minecraft, subway-s, temple-run, gta, fortnite, roblox, free-fire, custom"
}
{
"code": "not_authenticated",
"message": "Not authenticated",
"errorCode": "NOT_AUTHENTICATED",
"details": { "x-api-key": "Header not provided or API Key invalid" }
}
{
"code": "missing_credentials",
"message": "Your team uses its own provider keys (BYOK) and these are missing or invalid: OpenRouter, ElevenLabs. Add them in Settings → AI keys.",
"errorCode": "MISSING_CREDENTIALS",
"missingProviders": ["openrouter", "elevenlabs"],
"details": {}
}
{
"success": false,
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Not enough credits. Needed: 30, Available: 12",
"creditsNeeded": 30,
"creditsAvailable": 12
}
{
"code": "entitlement_required",
"error": "entitlement_required",
"message": "This endpoint requires the \"app\" product.",
"product": "app"
}
What happens next
- Keep the
projectId. - Either poll
GET /v1/project/{projectId}untilvideo.statusisCOMPLETED(or the projectstatusisfailed), or pass awebhookand wait for the call. - Download the file from
video.url(ordata.urlin the webhook).
Credits and your own keys
Teams on Hooked’s keys (managed): the project is priced fromtargetDuration (the gameplay background costs nothing on top of the narration). The PDF is read first, so a file that cannot be read costs nothing; then the credits are taken. If the balance is short, the request answers 402 INSUFFICIENT_CREDITS and nothing is created. If the project fails later, the credits are refunded automatically.
Teams on their own keys (BYOK): no credits are charged. The team needs OpenRouter (summary, captions) and ElevenLabs (voiceover) in Settings → AI keys. No Firecrawl key: Hooked downloads and reads the PDF itself. A missing key answers 402 missing_credentials with the list in missingProviders, before anything is created.
Errors
| Status | Body | When |
|---|---|---|
400 | { "success": false, "message": "<field>: <reason>" } | Validation failed: the PDF errors listed in PDF to Video (pdfUrl, pdfBase64, pdf: The PDF has no readable text …), summaryType, customPrompt, voiceId: Voice "…" not found., musicId: Music "…" not found., gameplaySettings.selectedGame: Invalid game "…", gameplaySettings.selectedVideo: Video "…" not found in your library (with custom), caption.preset: Invalid caption preset "…", webhook: Must be a valid HTTPS URL, Invalid JSON body. |
401 | not_authenticated | Missing or invalid x-api-key. |
402 | missing_credentials | BYOK team without the keys listed above. |
402 | subscription_required | Managed team without an active plan. |
402 | INSUFFICIENT_CREDITS | Managed team without enough credits. |
403 | entitlement_required | The team does not have the product this endpoint needs. |
500 | { "success": false, "message": "Failed to create PDF to Brainrot project: …" } | The text could not be summarised, or an unexpected error; the credits taken are refunded. |
Idempotency-Key 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.Next Steps
Get Project
PDF to Video
Reddit Story
List Voices
Authorizations
Headers
Makes the request safe to retry. 1-255 printable ASCII characters, one per operation (your job id, or a UUID you store), reused on every retry. Within 24 hours the same key with the same body answers with the first response and the header Idempotent-Replayed: true, without running or charging again. Keys are scoped to your team and the endpoint. 5xx answers and refusals before anything ran (401, 402, 403, 409, 429) are not kept. See Idempotency.
1 - 255^[\x20-\x7E]+$Body
The narrator: a library voice from /v1/voice/list or one of your team's custom voices. An ID that is neither answers 400 (voiceId: Voice "<id>" not found.).
1 - 30A public https link to the PDF (up to 20 MB). Hooked downloads it: private or local addresses, redirects to them, files over 20 MB and anything that is not a PDF answer 400. Send this or pdfBase64, not both.
2000The PDF file itself, base64-encoded (up to 10 MB once decoded; a data:application/pdf;base64, prefix is accepted). For bigger files use pdfUrl. Send this or pdfUrl, not both.
The file name, used to name the project. Taken from pdfUrl when omitted.
500How the text becomes the narration:
summarize: a short summary of the key points, abouttargetDurationseconds long.summarize_long: a detailed narration with supporting details, abouttargetDurationseconds long.key_as_is: the text as close to the original as possible, adapted for speech. The video is as long as the text needs;targetDurationis ignored.
summarize, summarize_long, key_as_is Approximate length of the video in seconds (10-600). Not used with key_as_is. It also sets the price of the project.
10 <= x <= 600summarize only: extra instructions for the summary, e.g. the angle or the audience (max 2,000 characters). Sent with another summaryType, it answers 400.
2000The gameplay clip in the background (default Minecraft).
Show child attributes
Show child attributes
Project name (max 100 characters). Generated from the source when omitted.
100Background music: a track from /v1/music/list or your team's own uploaded music. An ID that is neither answers 400 (musicId: Music "<id>" not found.).
30Vertical, horizontal or square.
ratio_9_16, ratio_16_9, ratio_1_1 Two-letter ISO 639-1 code of the narration. The narration is written in the document's own language, detected when omitted; this does not translate it.
Burned-in captions of the narration. Not used with motion-graphics (it typesets its own copy) or a speaking cast.
Show child attributes
Show child attributes
Narrator voice settings (ElevenLabs). Any field you leave out keeps its default. Not used by a speaking cast.
Show child attributes
Show child attributes
Add emoji/GIF stickers anchored to the captions. Not used with motion-graphics or a speaking cast.
Branding.
Show child attributes
Show child attributes
HTTPS URL on a public host, called when the video is ready or the project fails (max 500 characters). Requests are signed: verify the Hooked-Signature header (HMAC-SHA256 of ".") with the signing secret from Settings → Webhooks; Hooked-Event and Hooked-Delivery headers name the event and the delivery. No redirects are followed; 10 s timeout; 3 immediate attempts. See the Webhooks guide for the payload.
500Any JSON object of your own (max 5 KB). Stored on the project and sent back in the webhook payload. The key automationId is reserved for dashboard automations and is removed.