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

# Get Project

> Read the status of a project created through the API, and its video once rendered

## Overview

Every `POST /v1/project/create/*` endpoint answers with a `projectId`. Use this endpoint to follow that project until its video is ready.

A project goes through two stages:

1. **Generation**: script, voice, clips, captions. The project `status` is `processing` (or `draft` right after creation) and `video` is `null`.
2. **Render**: once the timeline is built, the project is rendered into a video. `video` is filled in, and `video.status` goes from `STARTED` to `COMPLETED` (or `FAILED`).

Every format starts its render when the project reaches `completed`; `video` stays `null` (or `STARTED`) for the minute or two the render takes.

The video file is ready when `video.status` is `COMPLETED`; `video.url` is the download link.

<Info>
  A project only shows up in [List Videos](/api-reference/video/list) once its render has started. Before that, find it here or in [List Projects](/api-reference/project/list), including a project that failed while generating.
</Info>

<RequestExample>
  ```bash cURL theme={null}
  curl "https://api.hooked.so/v1/project/cm4x9k2p10001ab12cd34ef56" \
    -H "x-api-key: your_api_key_here"
  ```

  ```javascript JavaScript theme={null}
  const projectId = "cm4x9k2p10001ab12cd34ef56";

  const response = await fetch(`https://api.hooked.so/v1/project/${projectId}`, {
    headers: { "x-api-key": process.env.HOOKED_API_KEY },
  });
  const { data: project } = await response.json();

  console.log(project.status, project.progress);
  if (project.video?.status === "COMPLETED") {
    console.log("Video ready:", project.video.url);
  }
  ```

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

  project_id = "cm4x9k2p10001ab12cd34ef56"
  response = requests.get(
      f"https://api.hooked.so/v1/project/{project_id}",
      headers={"x-api-key": os.environ["HOOKED_API_KEY"]},
  )
  project = response.json()["data"]

  print(project["status"], project["progress"])
  video = project["video"]
  if video and video["status"] == "COMPLETED":
      print("Video ready:", video["url"])
  ```
</RequestExample>

<ResponseExample>
  ```json 200 (rendered) theme={null}
  {
    "success": true,
    "message": "Project fetched successfully",
    "data": {
      "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"
      }
    }
  }
  ```

  ```json 200 (still generating) theme={null}
  {
    "success": true,
    "message": "Project fetched successfully",
    "data": {
      "id": "cm4x9k2p10001ab12cd34ef56",
      "name": "Morning routine tips",
      "type": "script_to_video",
      "status": "processing",
      "source": "api",
      "progress": 35,
      "message": "Generating media...",
      "usedCredits": 42,
      "metadata": {},
      "createdAt": "2026-10-01T09:12:03.000Z",
      "updatedAt": "2026-10-01T09:13:10.000Z",
      "video": null
    }
  }
  ```

  ```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 403 theme={null}
  {
    "code": "entitlement_required",
    "error": "entitlement_required",
    "message": "This endpoint requires the \"app\" product.",
    "product": "app"
  }
  ```

  ```json 404 theme={null}
  {
    "success": false,
    "message": "Project not found"
  }
  ```
</ResponseExample>

## Reading the status

| `status` | `video` | Meaning |
| - | - | - |
| `draft` / `processing` | `null` | Still generating. Keep polling. |
| `completed` | `null` | Generated; the render is about to start. Keep polling. |
| `processing` / `completed` | `status: "STARTED"` | Rendering. `video.url` is `""` until it finishes. |
| `completed` | `status: "COMPLETED"` | Done. Download `video.url`. |
| `failed` | `null` or any | The project failed. `message` says why. Credits charged for it are refunded automatically. |
| any | `status: "FAILED"` | The render failed. You can try again with [Render Project](/api-reference/video/render), which costs no credits. |

`video.url` and `video.thumbnail` are signed links that expire. Fetch the project or the video again for a fresh link instead of storing them.

## Polling

Poll every 10 to 15 seconds. Most projects finish in a few minutes; long or AI-video-heavy projects can take longer. Retrying this `GET` is always safe.

```javascript theme={null}
async function waitForVideo(projectId, { intervalMs = 10000, timeoutMs = 30 * 60 * 1000 } = {}) {
  const deadline = Date.now() + timeoutMs;

  while (Date.now() < deadline) {
    const response = await fetch(`https://api.hooked.so/v1/project/${projectId}`, {
      headers: { "x-api-key": process.env.HOOKED_API_KEY },
    });
    const { data: project } = await response.json();

    if (project.status === "failed") throw new Error(`Project failed: ${project.message}`);
    if (project.video?.status === "FAILED") throw new Error(`Render failed: ${project.video.message}`);
    if (project.video?.status === "COMPLETED") return project.video;
    // The render starts on its own once the project is completed: keep polling

    await new Promise((resolve) => setTimeout(resolve, intervalMs));
  }

  throw new Error("Timed out waiting for the video");
}
```

<Tip>
  Pass a `webhook` when you create the project and Hooked calls you when the video is ready or the project fails. See [Webhooks](/guides/webhooks).
</Tip>

## Errors

| Status | Body | Cause |
| - | - | - |
| 401 | `code: "not_authenticated"` | Missing or invalid `x-api-key`. |
| 403 | `code: "entitlement_required"` | Your team does not have the Hooked app product. |
| 404 | `Project not found` | The ID does not exist or belongs to another team. |
| 500 | `Internal server error` | Retry the request. |

## Related

<CardGroup cols={2}>
  <Card title="Creating videos" icon="plus" href="/guides/creating-videos">
    The create endpoints and the project lifecycle
  </Card>

  <Card title="Get Video Details" icon="info" href="/api-reference/video/details">
    Read a rendered video by its ID
  </Card>

  <Card title="Webhooks" icon="webhook" href="/guides/webhooks">
    Get notified instead of polling
  </Card>

  <Card title="Render Project" icon="clapperboard" href="/api-reference/video/render">
    Render a project again
  </Card>
</CardGroup>


## OpenAPI

````yaml GET /v1/project/{projectId}
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/{projectId}:
    get:
      tags:
        - Videos
      summary: Get Project
      description: >-
        Where a project stands. Every create endpoint answers a `projectId`;
        poll this until `video.status` is `COMPLETED`, or the project or its
        video is `failed`/`FAILED`. `video` is the latest render, or `null`
        until the render starts. A project appears in GET /v1/video/list only
        once its render starts.
      operationId: getProject
      parameters:
        - name: projectId
          in: path
          required: true
          schema:
            type: string
          description: The `projectId` a create endpoint answered.
          example: cm4x9k2p10001ab12cd34ef56
      responses:
        '200':
          description: The project
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data:
                    $ref: '#/components/schemas/Project'
              example:
                success: true
                message: Project fetched successfully
                data:
                  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'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/EntitlementRequired'
        '404':
          description: >-
            Not found. Another team's resource answers the same way as a missing
            one.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFound404'
              example:
                success: false
                message: Project not found
        '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.
    NotFound404:
      type: object
      description: >-
        Not found. Another team's resource answers the same way as a missing
        one.
      properties:
        success:
          type: boolean
          enum:
            - false
        message:
          type: string
      required:
        - success
        - message
      example:
        success: false
        message: Project not found
    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.