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

> Turn a PDF into a narrated video over gameplay footage

## Overview

PDF to Brainrot is [PDF to Video](/api-reference/video/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 public `https://` link, up to 20 MB, downloaded by Hooked) or `pdfBase64` (the file base64-encoded, up to 10 MB), not both. It needs a text layer: a scanned or image-only PDF answers `400` before anything is charged. See [Sending the PDF](/api-reference/video/pdf-to-video#overview).
* **The narration**: `summaryType` (`summarize` by default, `summarize_long` or `key_as_is`), `targetDuration` (default 30 seconds, not used with `key_as_is`) and `customPrompt` (with `summarize` only).
* **The background**: `gameplaySettings` (default `minecraft` / `minecraft-1`), or `selectedGame: "custom"` with a video of your library as `selectedVideo`. There is no `mediaType`: the background is always gameplay.

<Note>
  The request answers in a few seconds with a `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.
</Note>

```
POST https://api.hooked.so/v1/project/create/pdf-to-brainrot
```

***

<RequestExample>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```javascript JavaScript theme={null}
  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);
  ```

  ```python Python theme={null}
  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"])
  ```
</RequestExample>

### More request bodies

<CodeGroup>
  ```json The whole document, your own footage theme={null}
  {
    "pdfUrl": "https://example.com/notes/history-summary.pdf",
    "voiceId": "1004",
    "summaryType": "key_as_is",
    "gameplaySettings": { "selectedGame": "custom", "selectedVideo": "cm4x9k2p10001ab12cd34ef56" }
  }
  ```

  ```json Default background, horizontal theme={null}
  {
    "pdfUrl": "https://example.com/notes/chemistry-basics.pdf",
    "voiceId": "1004",
    "aspectRatio": "ratio_16_9"
  }
  ```
</CodeGroup>

***

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true,
    "data": {
      "projectId": "cm4x9k2p10001ab12cd34ef56",
      "status": "processing"
    },
    "message": "PDF to Brainrot successfully created"
  }
  ```

  ```json 400 theme={null}
  {
    "success": false,
    "message": "gameplaySettings.selectedGame: Invalid game \"tetris\". Allowed values are: minecraft, subway-s, temple-run, gta, fortnite, roblox, free-fire, custom"
  }
  ```

  ```json 401 theme={null}
  {
    "code": "not_authenticated",
    "message": "Not authenticated",
    "errorCode": "NOT_AUTHENTICATED",
    "details": { "x-api-key": "Header not provided or API Key invalid" }
  }
  ```

  ```json 402 missing keys theme={null}
  {
    "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": {}
  }
  ```

  ```json 402 credits theme={null}
  {
    "success": false,
    "errorCode": "INSUFFICIENT_CREDITS",
    "message": "Not enough credits. Needed: 30, Available: 12",
    "creditsNeeded": 30,
    "creditsAvailable": 12
  }
  ```

  ```json 403 theme={null}
  {
    "code": "entitlement_required",
    "error": "entitlement_required",
    "message": "This endpoint requires the \"app\" product.",
    "product": "app"
  }
  ```
</ResponseExample>

***

## What happens next

1. Keep the `projectId`.
2. Either poll [`GET /v1/project/{projectId}`](/api-reference/project/details) until `video.status` is `COMPLETED` (or the project `status` is `failed`), or pass a `webhook` and wait for the call.
3. Download the file from `video.url` (or `data.url` in the webhook).

See [Webhooks](/guides/webhooks) for every payload.

***

## Credits and your own keys

**Teams on Hooked's keys (managed):** the project is priced from `targetDuration` (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](/api-reference/video/pdf-to-video#errors) (`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. |

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

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Get Project" icon="circle-info" href="/api-reference/project/details">
    Follow the project until the video is ready
  </Card>

  <Card title="PDF to Video" icon="file-pdf" href="/api-reference/video/pdf-to-video">
    AI images, AI clips or your own media instead
  </Card>

  <Card title="Reddit Story" icon="reddit" href="/api-reference/video/reddit-story">
    Another format over gameplay
  </Card>

  <Card title="List Voices" icon="microphone" href="/api-reference/voice/list">
    Pick a voice
  </Card>
</CardGroup>


## OpenAPI

````yaml POST /v1/project/create/pdf-to-brainrot
openapi: 3.0.0
info:
  title: Hooked API
  version: 1.0.0
  description: AI Video Generation API
servers:
  - url: https://api.hooked.so
security:
  - ApiKeyAuth: []
tags:
  - name: Videos
    description: Create projects, follow them and render videos
  - name: Account Analysis
    description: Analyze TikTok and YouTube accounts
  - name: Channels
    description: Connected social accounts you publish to
  - name: Publish
    description: Schedule videos to your connected accounts
  - name: Images
    description: 'Image editing: background removal and expansion'
  - name: Media
    description: 'Your media library: list, import from a URL, upload a file'
  - name: Avatars
    description: Your team's own avatars (Actors) and their Hook + Demo reactions
paths:
  /v1/project/create/pdf-to-brainrot:
    post:
      tags:
        - Videos
      summary: Create PDF to Brainrot
      description: >-
        Turns a PDF into a narrated video over a gameplay clip (Minecraft,
        Subway Surfers style…): the text is extracted, summarised (or kept as
        is), voiced and captioned. Send the PDF as a link (`pdfUrl`) or inline
        (`pdfBase64`). The PDF needs a text layer: a scanned or image-only PDF,
        or one with a password, answers 400 before anything is charged. Answers
        in a few seconds with a `projectId` and status `processing`: the PDF is
        downloaded and its text checked in the request, and the scenes are
        written after the response, then the video is generated and rendered. If
        the scenes cannot be written, the project becomes `failed` with the
        reason in `message`, the credits are refunded and the failure webhook is
        sent. BYOK: needs OpenRouter and ElevenLabs. No Firecrawl key: the PDF
        is read by Hooked.


        A body that is not valid JSON answers 400 (`Invalid JSON body`). Create
        endpoints are not idempotent: do not retry a POST on a timeout or 5xx
        without first checking your projects, or you may create (and pay for)
        the video twice.
      operationId: createPdfToBrainrot
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - voiceId
              properties:
                pdfUrl:
                  type: string
                  format: uri
                  maxLength: 2000
                  description: >-
                    A 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.
                pdfBase64:
                  type: string
                  description: >-
                    The 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.
                pdfFileName:
                  type: string
                  maxLength: 500
                  description: >-
                    The file name, used to name the project. Taken from `pdfUrl`
                    when omitted.
                voiceId:
                  type: string
                  minLength: 1
                  maxLength: 30
                  description: >-
                    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.`).
                summaryType:
                  type: string
                  enum:
                    - summarize
                    - summarize_long
                    - key_as_is
                  default: summarize
                  description: >-
                    How the text becomes the narration:

                    - `summarize`: a short summary of the key points, about
                    `targetDuration` seconds long.

                    - `summarize_long`: a detailed narration with supporting
                    details, about `targetDuration` seconds 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; `targetDuration` is ignored.
                targetDuration:
                  type: integer
                  minimum: 10
                  maximum: 600
                  default: 30
                  description: >-
                    Approximate length of the video in seconds (10-600). Not
                    used with `key_as_is`. It also sets the price of the
                    project.
                customPrompt:
                  type: string
                  maxLength: 2000
                  description: >-
                    `summarize` only: extra instructions for the summary, e.g.
                    the angle or the audience (max 2,000 characters). Sent with
                    another `summaryType`, it answers 400.
                gameplaySettings:
                  type: object
                  description: The gameplay clip in the background (default Minecraft).
                  properties:
                    selectedGame:
                      type: string
                      enum:
                        - minecraft
                        - subway-s
                        - temple-run
                        - gta
                        - fortnite
                        - roblox
                        - free-fire
                        - custom
                      default: minecraft
                      description: >-
                        The game, or `custom` to play a video from your own
                        media library. Any other value answers 400.
                    selectedVideo:
                      type: string
                      default: minecraft-1
                      description: >-
                        The clip, as `<game>-<n>`: `minecraft-1`…`minecraft-9`,
                        `subway-s-1`…`subway-s-11`,
                        `temple-run-1`…`temple-run-8`, `gta-1`…`gta-12`,
                        `fortnite-1`…`fortnite-5`, `roblox-1`, `free-fire-1`. A
                        clip that does not exist plays the game's first clip.
                        With `selectedGame: "custom"`, the ID of a video in your
                        media library (400 when it is not one).
                name:
                  type: string
                  maxLength: 100
                  description: >-
                    Project name (max 100 characters). Generated from the source
                    when omitted.
                musicId:
                  type: string
                  maxLength: 30
                  description: >-
                    Background 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.`).
                aspectRatio:
                  type: string
                  enum:
                    - ratio_9_16
                    - ratio_16_9
                    - ratio_1_1
                  default: ratio_9_16
                  description: Vertical, horizontal or square.
                language:
                  type: string
                  description: >-
                    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.
                caption:
                  type: object
                  description: >-
                    Burned-in captions of the narration. Not used with
                    `motion-graphics` (it typesets its own copy) or a speaking
                    cast.
                  properties:
                    preset:
                      type: string
                      enum:
                        - default
                        - beast
                        - umi
                        - tiktok
                        - wrap1
                        - wrap2
                        - ariel
                        - hooked
                        - classic
                        - active
                        - bubble
                        - glass
                        - comic
                        - glow
                        - pastel
                        - neon
                        - retroTV
                        - red
                        - marker
                        - modern
                        - blue
                        - vivid
                      default: wrap1
                      description: Caption style. Any other value answers 400.
                    alignment:
                      type: string
                      enum:
                        - top
                        - middle
                        - bottom
                      default: bottom
                      description: Caption position.
                    disabled:
                      type: boolean
                      default: false
                      description: '`true` renders the video without captions.'
                audio:
                  type: object
                  description: >-
                    Narrator voice settings (ElevenLabs). Any field you leave
                    out keeps its default. Not used by a speaking cast.
                  properties:
                    speed:
                      type: number
                      minimum: 0.7
                      maximum: 1.2
                      default: 1
                      description: Speaking speed.
                    stability:
                      type: number
                      minimum: 0
                      maximum: 1
                      default: 0.5
                      description: Higher is steadier, lower is more expressive.
                    similarityBoost:
                      type: number
                      minimum: 0
                      maximum: 1
                      default: 0.75
                      description: How closely the voice sticks to the original.
                addStickers:
                  type: boolean
                  default: false
                  description: >-
                    Add emoji/GIF stickers anchored to the captions. Not used
                    with `motion-graphics` or a speaking cast.
                content:
                  type: object
                  description: Branding.
                  properties:
                    brandingLogo:
                      type: object
                      description: >-
                        Your logo over the whole video. Drawn only with
                        `enabled: true` and a `url`.
                      properties:
                        enabled:
                          type: boolean
                          default: false
                        url:
                          type: string
                          description: >-
                            Public URL of the logo image (PNG with transparency
                            works best).
                        position:
                          type: string
                          enum:
                            - top-left
                            - top-right
                            - bottom-left
                            - bottom-right
                          default: bottom-right
                        size:
                          type: number
                          minimum: 20
                          maximum: 200
                          default: 100
                          description: Logo size (20-200).
                webhook:
                  type: string
                  maxLength: 500
                  format: uri
                  description: >-
                    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
                    "<t>.<raw body>") 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.
                metadata:
                  type: object
                  additionalProperties: true
                  description: >-
                    Any 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.
            example:
              pdfUrl: https://example.com/notes/biology-chapter-3.pdf
              voiceId: '1'
              summaryType: summarize
              targetDuration: 60
              gameplaySettings:
                selectedGame: subway-s
                selectedVideo: subway-s-3
              webhook: https://yoursite.com/webhook?token=YOUR_SECRET
      responses:
        '200':
          description: >-
            Project created. Follow it with GET /v1/project/{projectId} or wait
            for the webhook.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectCreateResponse'
              example:
                success: true
                data:
                  projectId: clx9p2k4m0001abcd1234efgh
                  status: processing
                message: PDF to Brainrot successfully created
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/EntitlementRequired'
        '409':
          $ref: '#/components/responses/IdempotencyInProgress'
        '422':
          $ref: '#/components/responses/IdempotencyMismatch'
        '429':
          $ref: '#/components/responses/RateLimited429'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - ApiKeyAuth: []
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >-
        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](/guides/idempotency).
      schema:
        type: string
        minLength: 1
        maxLength: 255
        pattern: ^[\x20-\x7E]+$
      example: order-1042-video
  schemas:
    ProjectCreateResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          properties:
            projectId:
              type: string
              description: Follow it with GET /v1/project/{projectId}
            status:
              type: string
              enum:
                - draft
                - processing
                - completed
                - failed
              description: >-
                The project status right after creation, usually `draft` or
                `processing`. Follow it with GET /v1/project/{projectId}.
          required:
            - projectId
            - status
        message:
          type: string
      required:
        - success
        - data
        - message
    ValidationError400:
      type: object
      description: >-
        The request is invalid. `message` is `<field>: <reason>` when a field is
        to blame.
      properties:
        success:
          type: boolean
          enum:
            - false
        message:
          type: string
      required:
        - success
        - message
      example:
        success: false
        message: 'webhook: Must be a valid HTTPS URL'
    Error401:
      type: object
      description: Missing or invalid `x-api-key`.
      properties:
        code:
          type: string
          enum:
            - not_authenticated
        message:
          type: string
          example: Not authenticated
        errorCode:
          type: string
          enum:
            - NOT_AUTHENTICATED
        details:
          type: object
          additionalProperties:
            type: string
      example:
        code: not_authenticated
        message: Not authenticated
        errorCode: NOT_AUTHENTICATED
        details:
          x-api-key: Header not provided or API Key invalid
    MissingCredentials402:
      type: object
      description: >-
        BYOK team without the provider keys this request uses. Nothing was
        created or charged. Add the keys in Settings → AI keys.
      properties:
        success:
          type: boolean
          enum:
            - false
          description: Present when raised by the spend check
        code:
          type: string
          enum:
            - missing_credentials
        errorCode:
          type: string
          enum:
            - MISSING_CREDENTIALS
        missingProviders:
          type: array
          items:
            type: string
            enum:
              - openrouter
              - elevenlabs
              - gemini
              - bria
              - firecrawl
        message:
          type: string
        details:
          type: object
      required:
        - code
        - errorCode
        - missingProviders
        - message
      example:
        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: {}
    SubscriptionRequired402:
      type: object
      description: Managed team (generates with Hooked's keys) without a live subscription.
      properties:
        success:
          type: boolean
          enum:
            - false
        code:
          type: string
          enum:
            - subscription_required
        errorCode:
          type: string
          enum:
            - SUBSCRIPTION_REQUIRED
        message:
          type: string
      required:
        - success
        - code
        - errorCode
        - message
      example:
        success: false
        code: subscription_required
        errorCode: SUBSCRIPTION_REQUIRED
        message: >-
          Your team generates with Hooked's keys, which are paid by your plan.
          Subscribe to keep generating.
    InsufficientCredits402:
      type: object
      description: >-
        Not enough credits for this request. `creditsNeeded` /
        `creditsAvailable` are included when the estimate is known.
      properties:
        success:
          type: boolean
          enum:
            - false
        errorCode:
          type: string
          enum:
            - INSUFFICIENT_CREDITS
        message:
          type: string
        creditsNeeded:
          type: number
        creditsAvailable:
          type: number
      required:
        - success
        - errorCode
        - message
      example:
        success: false
        errorCode: INSUFFICIENT_CREDITS
        message: Not enough credits to perform this action
        creditsNeeded: 40
        creditsAvailable: 12
    EntitlementRequired403:
      type: object
      description: >-
        Valid key, but the team does not have the product this endpoint belongs
        to.
      properties:
        code:
          type: string
          enum:
            - entitlement_required
        error:
          type: string
          enum:
            - entitlement_required
        message:
          type: string
        product:
          type: string
          example: app
      required:
        - code
        - error
        - message
        - product
      example:
        code: entitlement_required
        error: entitlement_required
        message: This endpoint requires the "app" product.
        product: app
    Error500:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
        message:
          type: string
        error:
          type: string
          description: Present on some endpoints
      example:
        success: false
        message: Internal server error
  responses:
    ValidationError:
      description: Validation error (also `Invalid JSON body`)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError400'
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error401'
    PaymentRequired:
      description: >-
        Payment required: a BYOK team is missing provider keys (nothing was
        created or charged), a managed team has no live subscription, or there
        are not enough credits.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/MissingCredentials402'
              - $ref: '#/components/schemas/SubscriptionRequired402'
              - $ref: '#/components/schemas/InsufficientCredits402'
          examples:
            missingCredentials:
              value:
                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: {}
            subscriptionRequired:
              value:
                success: false
                code: subscription_required
                errorCode: SUBSCRIPTION_REQUIRED
                message: >-
                  Your team generates with Hooked's keys, which are paid by your
                  plan. Subscribe to keep generating.
            insufficientCredits:
              value:
                success: false
                errorCode: INSUFFICIENT_CREDITS
                message: Not enough credits to perform this action
                creditsNeeded: 40
                creditsAvailable: 12
    EntitlementRequired:
      description: The team does not have the product this endpoint needs
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/EntitlementRequired403'
    IdempotencyInProgress:
      description: >-
        A request with the same `Idempotency-Key` is still running. Retry in a
        few seconds.
      content:
        application/json:
          schema:
            type: object
            properties:
              success:
                type: boolean
                enum:
                  - false
              code:
                type: string
              message:
                type: string
              details:
                type: object
                additionalProperties: true
            required:
              - success
              - code
              - message
          example:
            success: false
            code: idempotency_conflict
            message: A request with this Idempotency-Key is still in progress
            details: {}
    IdempotencyMismatch:
      description: >-
        The `Idempotency-Key` was already used with a different body. Use a new
        key for a new request.
      content:
        application/json:
          schema:
            type: object
            properties:
              success:
                type: boolean
                enum:
                  - false
              code:
                type: string
              message:
                type: string
              details:
                type: object
                additionalProperties: true
            required:
              - success
              - code
              - message
          example:
            success: false
            code: idempotency_mismatch
            message: Idempotency-Key was used with a different request
            details: {}
    RateLimited429:
      description: >-
        Your team is over a rate limit: 30 POST requests per minute, 120 other
        requests per minute, or 3 downloads of your URLs running at once.
        Nothing ran and nothing was charged: wait `Retry-After` seconds and send
        the request again. See [Rate
        limits](/guides/error-handling#rate-limits-429).
      headers:
        Retry-After:
          description: Seconds to wait before sending again.
          schema:
            type: integer
        X-RateLimit-Limit:
          description: The limit of the bucket you hit (not sent for the download cap).
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: 'Requests left in this window: 0 (not sent for the download cap).'
          schema:
            type: integer
        X-RateLimit-Reset:
          description: >-
            When the window ends, in Unix seconds (not sent for the download
            cap).
          schema:
            type: integer
      content:
        application/json:
          schema:
            type: object
            properties:
              success:
                type: boolean
                enum:
                  - false
              errorCode:
                type: string
                enum:
                  - RATE_LIMITED
              message:
                type: string
              retryAfterSeconds:
                type: integer
            required:
              - success
              - errorCode
              - message
              - retryAfterSeconds
          example:
            success: false
            errorCode: RATE_LIMITED
            message: >-
              Rate limit exceeded: 30 POST requests per minute per team. Retry
              after 12 s.
            retryAfterSeconds: 12
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error500'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````

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