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

# List Projects

> List your team's projects, newest first, with their latest video

## Overview

Returns your team's projects, newest first, with the total count for paging. Projects made through the API and projects made in the dashboard are both listed; templates are not.

Each item is the same object [Get Project](/api-reference/project/details) returns: its `status`, `progress`, the `metadata` you sent, and its latest render under `video` (`null` until the render starts). Unlike [List Videos](/api-reference/video/list), a project is listed from the moment it is created, so you can find one that is still generating or that failed before rendering.

<RequestExample>
  ```bash cURL theme={null}
  curl "https://api.hooked.so/v1/project/list?type=script-to-video&status=completed&limit=20" \
    -H "x-api-key: your_api_key_here"
  ```

  ```javascript JavaScript theme={null}
  const params = new URLSearchParams({ type: "script-to-video", status: "completed", limit: "20" });
  const response = await fetch(`https://api.hooked.so/v1/project/list?${params}`, {
    headers: { "x-api-key": process.env.HOOKED_API_KEY },
  });
  const { data } = await response.json();

  console.log(`${data.projects.length} of ${data.total} projects`);
  for (const project of data.projects) {
    console.log(project.name, project.status, project.video?.url ?? "(no video yet)");
  }
  ```

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

  response = requests.get(
      "https://api.hooked.so/v1/project/list",
      params={"type": "script-to-video", "status": "completed", "limit": 20},
      headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
  )
  data = response.json()["data"]
  print(f"{len(data['projects'])} of {data['total']} projects")
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true,
    "message": "Projects fetched successfully",
    "data": {
      "projects": [
        {
          "id": "cm4x9q1r30005ab12gh34ij78",
          "name": "Evening routine tips",
          "type": "script_to_video",
          "status": "processing",
          "source": "api",
          "progress": 35,
          "message": "Generating media...",
          "usedCredits": 42,
          "metadata": {},
          "createdAt": "2026-10-01T11:02:00.000Z",
          "updatedAt": "2026-10-01T11:03:10.000Z",
          "video": null
        },
        {
          "id": "cm4x9k2p10001ab12cd34ef56",
          "name": "Morning routine tips",
          "type": "script_to_video",
          "status": "completed",
          "source": "api",
          "progress": 100,
          "message": "Video ready",
          "usedCredits": 42,
          "metadata": { "campaignId": "spring-launch" },
          "createdAt": "2026-10-01T09:12:03.000Z",
          "updatedAt": "2026-10-01T09:16:41.000Z",
          "video": {
            "id": "cm4x9n7q20003ab12xy98zt10",
            "name": "Morning routine tips",
            "projectId": "cm4x9k2p10001ab12cd34ef56",
            "projectType": "script_to_video",
            "status": "COMPLETED",
            "progress": 100,
            "message": "Video ready",
            "url": "https://files.hooked.so/team/videos/cm4x9n7q20003ab12xy98zt10.mp4?X-Amz-Signature=...",
            "thumbnail": "https://files.hooked.so/team/thumbnails/cm4x9n7q20003ab12xy98zt10.jpg?X-Amz-Signature=...",
            "durationInFrames": 1125,
            "durationInSeconds": 45,
            "size": 18874368,
            "createdAt": "2026-10-01T09:15:58.000Z",
            "updatedAt": "2026-10-01T09:16:41.000Z"
          }
        }
      ],
      "total": 12,
      "limit": 2,
      "offset": 0
    }
  }
  ```

  ```json 400 theme={null}
  {
    "success": false,
    "message": "status: Must be one of draft, processing, completed, failed"
  }
  ```
</ResponseExample>

## Filtering by format

`type` takes the name of the create endpoint that made the project. Each project answers with its stored `type`, and that value works as a filter too.

| `type` filter | Stored `type` |
| - | - |
| `script-to-video` | `script_to_video` |
| `prompt-to-video` | `prompt_to_video` |
| `ugc-ads`, `ugc-studio` | `ugc_ads` (every UGC Studio format) |
| `talking-avatar` | `ugc_video` |
| `hook-demo` | `hook_demo` |
| `podcast-interview` | `podcast_interview` |
| `scenes` | `scenes` |
| `tiktok-slideshow` | `tiktok_slideshow` |
| `quiz-video` | `quiz_video` |
| `reddit-story` | `reddit_story` |
| `scrolling-video` | `scrolling_video` |
| `article-to-video` | `article_to_video` |
| `pdf-to-video` | `pdf_to_video` |
| `pdf-to-brainrot` | `pdf_to_brainrot` |
| `music-to-video` | `music_to_video` |
| `clone-video` | `clone_video` |
| `cinematic` | `cinematic` |
| `add-captions` | `caption_video` |
| `extend-video` | `extend_video` |
| `remove-background` | `remove_background` |

Projects of an older dashboard format (`product_ads`) are listed and can be filtered by their stored type.

`source=api` keeps only the projects made through the API; `source=web` only the ones made in the dashboard (its automations included).

## Paging

Pass `limit` (1 to 100, default 50) and `offset`. You have every project when `offset + limit >= total`. `video.url` is a signed link that expires: read the project again for a fresh one instead of storing it.

## Errors

| Status | When |
| - | - |
| 400 | Unknown `status`, `type` or `source`. `message` names the parameter |
| 401 | Missing or invalid API key (`code: "not_authenticated"`) |
| 403 | The team does not have the Hooked app product (`code: "entitlement_required"`) |
| 500 | `Failed to fetch projects`. Retry the request |


## OpenAPI

````yaml GET /v1/project/list
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/list:
    get:
      tags:
        - Videos
      summary: List Projects
      description: >-
        Your team's projects, newest first, with the total count for paging: the
        ones made through the API and the ones made in the dashboard. Each item
        is the object GET /v1/project/{projectId} returns, with its latest
        render under `video`. Unlike List Videos, a project is listed from the
        moment it is created, including one that failed before rendering.
        Templates are left out.
      operationId: listProjects
      parameters:
        - name: status
          in: query
          required: false
          description: >-
            Only projects in this state. Case-insensitive; any other value
            answers 400.
          schema:
            type: string
            enum:
              - draft
              - processing
              - completed
              - failed
        - name: type
          in: query
          required: false
          description: >-
            Only projects of this format, named by its create endpoint:
            `add-captions`, `article-to-video`, `cinematic`, `clone-video`,
            `extend-video`, `hook-demo`, `music-to-video`, `pdf-to-brainrot`,
            `pdf-to-video`, `podcast-interview`, `prompt-to-video`,
            `quiz-video`, `reddit-story`, `remove-background`, `scenes`,
            `script-to-video`, `scrolling-video`, `talking-avatar`,
            `tiktok-slideshow`, `ugc-ads`, `ugc-studio`. `ugc-ads` and
            `ugc-studio` are the same type (`ugc_ads`), so either lists both.
            The stored type a project answers with (`script_to_video`,
            `caption_video`, …) works too. `all` or left out: every type. Any
            other value answers 400.
          schema:
            type: string
            default: all
          example: script-to-video
        - name: source
          in: query
          required: false
          description: >-
            Only projects made through the API (`api`) or in the dashboard
            (`web`). Both when left out; any other value answers 400.
          schema:
            type: string
            enum:
              - api
              - web
        - name: limit
          in: query
          required: false
          description: >-
            Page size, 1 to 100. Larger values are capped at 100; missing or
            invalid values fall back to 50.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          required: false
          description: >-
            Projects to skip. Missing or invalid values fall back to 0. You are
            done when `offset + limit >= total`.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Projects
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    type: object
                    properties:
                      projects:
                        type: array
                        items:
                          $ref: '#/components/schemas/Project'
                        description: The projects on this page.
                      total:
                        type: integer
                        description: How many projects match the filters, across all pages.
                      limit:
                        type: integer
                        description: The page size used.
                      offset:
                        type: integer
                        description: The offset used.
              example:
                success: true
                message: Projects fetched successfully
                data:
                  projects:
                    - id: cm4x9q1r30005ab12gh34ij78
                      name: Morning routine tips
                      type: script_to_video
                      status: processing
                      source: api
                      progress: 35
                      message: Generating media...
                      usedCredits: 42
                      metadata: {}
                      createdAt: '2026-10-01T11:02:00.000Z'
                      updatedAt: '2026-10-01T11:03:10.000Z'
                      video: null
                    - id: cm4x9k2p10001ab12cd34ef56
                      name: Serum ad
                      type: ugc_ads
                      status: completed
                      source: api
                      progress: 100
                      message: Video completed
                      usedCredits: 40
                      metadata:
                        campaignId: campaign_42
                      createdAt: '2026-10-01T10:00:00.000Z'
                      updatedAt: '2026-10-01T10:06:00.000Z'
                      video:
                        id: cm4x9n7q20003ab12xy98zt10
                        name: Serum ad
                        projectId: cm4x9k2p10001ab12cd34ef56
                        projectType: ugc_ads
                        status: COMPLETED
                        progress: 100
                        message: Video ready
                        url: >-
                          https://files.hooked.so/team/videos/cm4x9n7q20003ab12xy98zt10.mp4?X-Amz-Signature=...
                        thumbnail: >-
                          https://files.hooked.so/team/thumbnails/cm4x9n7q20003ab12xy98zt10.jpg?X-Amz-Signature=...
                        durationInFrames: 750
                        durationInSeconds: 30
                        size: 15728640
                        createdAt: '2026-10-01T10:05:00.000Z'
                        updatedAt: '2026-10-01T10:06:00.000Z'
                  total: 12
                  limit: 2
                  offset: 0
        '400':
          description: Unknown `status`, `type` or `source`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError400'
              example:
                success: false
                message: 'status: Must be one of draft, processing, completed, failed'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/EntitlementRequired'
        '429':
          $ref: '#/components/responses/RateLimited429'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    Project:
      type: object
      description: >-
        A project and where it stands. `video` is its latest render, or null
        while there is none.
      properties:
        id:
          type: string
          description: The project ID (the `projectId` a create endpoint answered).
        name:
          type: string
          description: >-
            The project name: the `name` you sent, or one generated from the
            content.
        type:
          type: string
          enum:
            - ugc_video
            - ugc_ads
            - hook_demo
            - script_to_video
            - prompt_to_video
            - tiktok_slideshow
            - podcast_interview
            - scenes
            - cinematic
            - caption_video
            - remove_background
            - extend_video
            - scrolling_video
            - music_to_video
            - article_to_video
            - pdf_to_video
            - pdf_to_brainrot
            - reddit_story
            - quiz_video
            - clone_video
            - class
            - product_ads
          description: >-
            The stored project type. Create endpoint → type: `add-captions` →
            `caption_video`, `article-to-video` → `article_to_video`,
            `cinematic` → `cinematic`, `clone-video` → `clone_video`,
            `extend-video` → `extend_video`, `hook-demo` → `hook_demo`,
            `music-to-video` → `music_to_video`, `pdf-to-brainrot` →
            `pdf_to_brainrot`, `pdf-to-video` → `pdf_to_video`,
            `podcast-interview` → `podcast_interview`, `prompt-to-video` →
            `prompt_to_video`, `quiz-video` → `quiz_video`, `reddit-story` →
            `reddit_story`, `remove-background` → `remove_background`, `scenes`
            → `scenes`, `script-to-video` → `script_to_video`, `scrolling-video`
            → `scrolling_video`, `talking-avatar` → `ugc_video`,
            `tiktok-slideshow` → `tiktok_slideshow`, `ugc-ads` → `ugc_ads`,
            `ugc-studio` → `ugc_ads`. `product_ads` is a dashboard-only (older)
            format.
        status:
          type: string
          enum:
            - draft
            - processing
            - completed
            - failed
          description: >-
            `draft` right after creation, `processing` while generating,
            `completed` once generated (the render then starts on its own),
            `failed` when generation failed (the credits it charged are
            refunded).
        source:
          type: string
          enum:
            - api
            - web
          description: >-
            Where the project was made: `api` (a create endpoint) or `web` (the
            dashboard, including its automations).
        progress:
          type: number
          description: Generation progress, 0 to 100.
        message:
          type: string
          nullable: true
          description: The current step, or why the project failed.
        usedCredits:
          type: number
          description: >-
            Credits charged for this project so far. `0` once a failed project
            is refunded; usually `0` for teams on their own provider keys
            (BYOK).
        metadata:
          type: object
          additionalProperties: true
          description: The `metadata` object you sent on create, or `{}`.
        createdAt:
          type: string
          format: date-time
          description: When the project was created.
        updatedAt:
          type: string
          format: date-time
          description: When the project last changed.
        video:
          type: object
          allOf:
            - $ref: '#/components/schemas/Video'
          nullable: true
          description: >-
            The project's latest render, the same object GET /v1/video/{videoId}
            returns. `null` until the render starts.
    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'
    Video:
      type: object
      description: >-
        A rendered video. A video exists once a project's render has started;
        each render of a project is a new video.
      properties:
        id:
          type: string
          example: cm4x9n7q20003ab12xy98zt10
          description: The video ID.
        name:
          type: string
          example: Serum ad
          description: 'The video name: the name of the project it was rendered from.'
        projectId:
          type: string
          nullable: true
          description: >-
            The project this video was rendered from (the `projectId` a create
            endpoint answered).
        projectType:
          type: string
          nullable: true
          enum:
            - ugc_video
            - ugc_ads
            - hook_demo
            - script_to_video
            - prompt_to_video
            - tiktok_slideshow
            - podcast_interview
            - scenes
            - cinematic
            - caption_video
            - remove_background
            - extend_video
            - scrolling_video
            - music_to_video
            - article_to_video
            - pdf_to_video
            - pdf_to_brainrot
            - reddit_story
            - quiz_video
            - clone_video
          description: >-
            That project's type. API formats: `script_to_video`,
            `prompt_to_video`, `ugc_ads`, `ugc_video` (Talking Avatar),
            `hook_demo`, `scenes`, `tiktok_slideshow`, `caption_video` (Add
            Captions), `extend_video`, `remove_background`, `podcast_interview`,
            `quiz_video`, `reddit_story`, `scrolling_video`. Dashboard-only
            formats: `cinematic`, `article_to_video`, `pdf_to_video`,
            `pdf_to_brainrot`, `music_to_video`, `clone_video`.
        status:
          type: string
          enum:
            - STARTED
            - COMPLETED
            - FAILED
          description: >-
            `STARTED` while rendering, `COMPLETED` when the file is ready,
            `FAILED` when the render failed or was canceled. A failed render can
            be retried with POST /v1/render, at no cost.
        progress:
          type: number
          example: 100
          description: Render progress, 0 to 100.
        message:
          type: string
          nullable: true
          example: Video ready
          description: >-
            The current step (`Rendering video...`, `Video ready`), or why the
            render failed (`Rendering failed`, `Rendering timed out`).
        url:
          type: string
          description: >-
            Signed download link of the MP4. Empty string (`""`) until the video
            is `COMPLETED`. The link expires: request the video again for a
            fresh one instead of storing it.
        thumbnail:
          type: string
          description: >-
            Signed link of the thumbnail image, or `""` while there is none. It
            is generated shortly after the video completes, and expires like
            `url`.
        durationInFrames:
          type: number
          example: 750
          description: >-
            Length in frames, at 25 frames per second. `0` until the video is
            ready.
        durationInSeconds:
          type: number
          example: 30
          description: >-
            Length in seconds (`durationInFrames / 25`). `0` until the video is
            ready.
        size:
          type: number
          example: 15728640
          description: File size in bytes, or `0` when unknown.
        createdAt:
          type: string
          format: date-time
          description: When the render started.
        updatedAt:
          type: string
          format: date-time
          description: When the video last changed.
    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
    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:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error401'
    EntitlementRequired:
      description: The team does not have the product this endpoint needs
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/EntitlementRequired403'
    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.