{
  "openapi": "3.0.3",
  "info": {
    "title": "VidNavigator Developer API",
    "description": "The VidNavigator Developer API provides programmatic access to video analysis, transcription, and search capabilities.\n\n## Authentication\nAll endpoints require API key authentication via the `X-API-Key` header:\n```\nX-API-Key: YOUR_API_KEY\n```\n\n## Rate Limits\nCheck the documentation for the rate limits for each endpoint.\n\n## Error Handling\nThe API uses standard HTTP status codes and returns error responses in JSON format:\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"error_type\",\n  \"message\": \"Human readable error message\"\n}\n```",
    "version": "1.0.0",
    "contact": {
      "name": "VidNavigator Support",
      "url": "https://vidnavigator.com/support",
      "email": "support@vidnavigator.com"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://vidnavigator.com/terms"
    }
  },
  "servers": [
    {
      "url": "https://api.vidnavigator.com/v1",
      "description": "Production server"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/transcript": {
      "post": {
        "summary": "Get transcript for any supported video",
        "description": "Extract transcript from any supported online video (YouTube, Vimeo, Twitter/X, TikTok, Facebook, Dailymotion, Loom, etc.) with optional language selection. The endpoint auto-detects the platform from the URL and routes internally.\n\n The URL inside `video_url` is what determines the upstream behavior.\n\n**Note:** for Instagram videos, use `/transcribe` instead (speech-to-text).\n\nOptions:\n- `transcript_text=true`: Returns the transcript as a single plain-text string instead of an array of segments.\n- `metadata_only=true`: Returns only video metadata (no transcript).\n- `fallback_to_metadata=true`: If transcript is unavailable, returns video metadata with an empty transcript instead of a 404 error (ignored when `metadata_only=true`).\n\n**Billing** depends on the URL:\n- YouTube (full transcript OR `metadata_only=true`) \u2192 1\u00d7 `residential_request` (the transcript fetch routes through the residential proxy)\n- Non-YouTube \u2192 1\u00d7 `standard_request` (some platforms internally use residential and would be billed accordingly)\n\nThe charge applies whether or not a transcript is returned (mirrors the proxy hop we paid for). Set `include_usage=true` to receive a `usage` block in the response.",
        "operationId": "getTranscript",
        "tags": [
          "Transcripts"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "video_url"
                ],
                "properties": {
                  "video_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL of the video to retrieve transcript for",
                    "example": "https://twitter.com/user/status/123456789"
                  },
                  "language": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 2,
                    "description": "ISO2 language code (optional)",
                    "example": "en"
                  },
                  "metadata_only": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, returns only video metadata without transcript. Usage is still recorded."
                  },
                  "fallback_to_metadata": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, returns video metadata with an empty transcript if transcript is unavailable (200). Ignored if metadata_only is true."
                  },
                  "transcript_text": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, returns the transcript as a single plain-text string instead of an array of segments."
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, the response includes a `usage` block listing every meter charged during this request, the total credits deducted, and the user's remaining balance."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transcript retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "video_info": {
                          "$ref": "#/components/schemas/VideoInfo"
                        },
                        "transcript": {
                          "$ref": "#/components/schemas/TranscriptOutput"
                        }
                      }
                    },
                    "usage": {
                      "$ref": "#/components/schemas/UsageBlock"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/VideoNotFound"
          },
          "403": {
            "$ref": "#/components/responses/ContentRestricted"
          },
          "451": {
            "$ref": "#/components/responses/GeoRestricted"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/transcribe": {
      "post": {
        "summary": "Transcribe online videos",
        "description": "Transcribe online videos (e.g., Instagram, TikTok) using speech-to-text models when a transcript is not available.\n\n**Instagram Carousel Posts:**\n- For posts with multiple videos, the URL can include `?img_index=N` to select a specific video\n- Example: `https://www.instagram.com/p/ABC123/?img_index=2` transcribes the second video\n- Without `img_index`, the first video is transcribed\n- Set `all_videos=true` to transcribe ALL videos in a carousel post\n\n**Options:**\n- `transcript_text=true`: Returns transcript as a single text string instead of segments\n- `all_videos=true`: For carousel posts, returns all videos with their transcripts\n\n**Billing:** on cache miss, one `standard_request` or `residential_request` is charged for the metadata fetch (residential for Instagram, Facebook-watch, Rumble; standard otherwise), plus `transcription_hour` proportional to the audio duration. On cache hit (same URL transcribed before for any user), only `transcription_hour` is charged \u2014 the metadata fetch is skipped. Users with the `free_on_cache_hit` sponsorship flag get the cache-hit `transcription_hour` waived (`waived: true` in the per-charge entry). Set `include_usage=true` to see the breakdown.",
        "operationId": "transcribeVideo",
        "tags": [
          "Transcripts"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "video_url"
                ],
                "properties": {
                  "video_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL of the video to transcribe. For Instagram carousel posts, append ?img_index=N to select a specific video.",
                    "example": "https://www.instagram.com/reel/C86ZvEaqRmo/"
                  },
                  "transcript_text": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, returns the transcript as a single plain-text string instead of an array of segments."
                  },
                  "all_videos": {
                    "type": "boolean",
                    "default": false,
                    "description": "For carousel posts only. When true, transcribes ALL videos in the post and returns them in an array. Ignored for non-carousel URLs (reels, TikTok, etc.)."
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, the response includes a `usage` block listing every meter charged during this request, the total credits deducted, and the user's remaining balance."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Video transcribed successfully. Response format depends on `all_videos` parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "description": "Single video response (default)",
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "success"
                          ]
                        },
                        "data": {
                          "type": "object",
                          "properties": {
                            "video_info": {
                              "$ref": "#/components/schemas/VideoInfo"
                            },
                            "transcript": {
                              "$ref": "#/components/schemas/TranscriptOutput"
                            }
                          }
                        },
                        "usage": {
                          "$ref": "#/components/schemas/UsageBlock"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "description": "All videos response (when all_videos=true)",
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "success"
                          ]
                        },
                        "data": {
                          "type": "object",
                          "properties": {
                            "carousel_info": {
                              "$ref": "#/components/schemas/CarouselInfo"
                            },
                            "videos": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/CarouselVideoResult"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request - invalid parameters or no videos found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "missing_parameter",
                        "invalid_parameter",
                        "no_videos_found",
                        "unsupported_platform"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/tiktok/profile": {
      "post": {
        "summary": "Submit a TikTok profile scrape (async)",
        "description": "Start an asynchronous TikTok profile scrape and return a `task_id` immediately.\n\nThe scrape runs in the background with shallow extraction so it can stop early when filters are satisfied. Results are retained for 1 hour and, when cloud storage is configured, also uploaded as a single JSON file accessible through `download_url`.\n\n**Billing:** Each TikTok API page fetched is billed as one standard request. One page is pre-charged when the task is accepted; remaining pages are reconciled when the scrape completes. If the scrape fails after charging additional pages, all charged pages are reversed. TikTok currently returns ~15 videos per page; this is an internal estimate and may change.\n\n**Filters:** All filters are applied as the scraper iterates. `max_posts` and `after_datetime` allow the scraper to stop fetching new pages early; the other filters reduce result size but do not stop pagination.\n\n**Usage disclosure:** This endpoint does NOT accept `include_usage`. Because billing happens in the background worker after the 202 response is sent, there is nothing to disclose at submit time. Pass `include_usage=true` on `GET /tiktok/profile/{task_id}` instead \u2014 the GET endpoint replays the final charges once `task_status=completed`.\n\nUse `GET /tiktok/profile/{task_id}` to poll status and retrieve paginated results, or follow `download_url` for the complete JSON.",
        "operationId": "submitTikTokProfileScrape",
        "tags": [
          "TikTok"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "profile_url"
                ],
                "properties": {
                  "profile_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public TikTok profile URL (e.g. https://www.tiktok.com/@username).",
                    "example": "https://www.tiktok.com/@tiktok"
                  },
                  "max_posts": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Maximum number of matching videos to return. The scraper stops paginating once this is reached.",
                    "example": 100
                  },
                  "after_datetime": {
                    "type": "string",
                    "description": "Only include videos published on or after this boundary. Accepts either YYYY-MM-DD or an ISO datetime with timezone (e.g. 2024-01-01T00:00:00Z). Filtering uses per-video `timestamp` when available (UTC), with upload-date fallback otherwise.",
                    "example": "2024-01-01T00:00:00Z"
                  },
                  "before_datetime": {
                    "type": "string",
                    "description": "Only include videos published on or before this boundary. Accepts either YYYY-MM-DD or an ISO datetime with timezone (e.g. 2024-12-31T23:59:59+02:00). Filtering uses per-video `timestamp` when available (UTC), with upload-date fallback otherwise.",
                    "example": "2024-12-31"
                  },
                  "min_likes": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Only include videos with at least this many likes."
                  },
                  "max_likes": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Only include videos with at most this many likes."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Task accepted and queued for background scraping.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "task_id": {
                          "type": "string"
                        },
                        "task_status": {
                          "type": "string",
                          "enum": [
                            "processing"
                          ]
                        },
                        "profile_url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "check_status_url": {
                          "type": "string",
                          "description": "Relative URL to poll for status and results.",
                          "example": "/v1/tiktok/profile/550e8400-e29b-41d4-a716-446655440000"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (missing or malformed `profile_url`, bad filter values, non-TikTok URL).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "request_body_required",
                        "missing_parameter",
                        "invalid_url",
                        "invalid_parameter"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/tiktok/profile/{task_id}": {
      "get": {
        "summary": "Get TikTok profile scrape result",
        "description": "Poll an async TikTok profile scrape task and retrieve a cursor-paginated page of videos.\n\n**Cursor pagination:** The cursor encodes an absolute offset, so it is safe to change `limit` between calls without skipping or duplicating videos. Pass the `next_cursor` returned from a previous response as `cursor` in the next request. Omit `cursor` to start from the beginning.\n\n**TTL:** Tasks (and their `download_url` availability) expire ~1 hour after creation. Poll while `task_status` is `processing`, and switch to `download_url` for very large profiles.\n\nThis endpoint does not consume credits \u2014 polling is free.\n\n**Usage disclosure:** Set `include_usage=true` to receive a `usage` block describing the charges the background worker recorded for this task. Returned only when `task_status=completed` (processing tasks haven't finished billing; failed tasks have all charges refunded). Charges are `standard_request` units, one per TikTok page consumed.",
        "operationId": "getTikTokProfileScrape",
        "tags": [
          "TikTok"
        ],
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "description": "ID returned by POST /tiktok/profile.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor returned as `next_cursor` from the previous response. Omit to fetch the first page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of videos to include in the response.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "include_usage",
            "in": "query",
            "required": false,
            "description": "When true and `task_status=completed`, attach a `usage` block describing the charges the background worker recorded (`standard_request` × `pages_consumed`).",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Task found. `task_status` may be `processing`, `completed`, or `failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/TikTokProfileTask"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid cursor.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_cursor"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Task not found, expired (>1h), or not owned by this user.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "task_not_found"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/tiktok/search": {
      "post": {
        "summary": "Submit a TikTok keyword search (async)",
        "description": "Start an asynchronous TikTok keyword search task and return a `task_id` immediately. Poll `GET /tiktok/search/{task_id}` for cursor-paginated results. Results are retained for ~1 hour.\n\nResults are returned sorted by `published_at` descending (newest first) across pagination, full retrieval, and the signed `download_url` payload.\n\n**parallel_search_slices billing:** by default (`1`), the search runs a single paginated query chain and is naturally capped near 120 items by TikTok. Setting `parallel_search_slices` to `2..4` runs that many concurrent chains in parallel and dedups by video id, lifting the effective ceiling. Cost scales linearly: each additional slice consumes up to ~1x the residential pages of a single search, so an N-slice request bills up to **Nx the residential pages**. `parallel_search_slices` is *not* a time filter \u2014 use `after_datetime` / `before_datetime` for time windows.\n\n**Usage disclosure:** This endpoint does NOT accept `include_usage`. Billing happens in the background worker after the 202 response is sent, so there is nothing to disclose at submit time. Pass `include_usage=true` on `GET /tiktok/search/{task_id}` instead \u2014 the GET endpoint replays the final charges once `task_status=completed`.",
        "operationId": "submitTikTokSearch",
        "tags": [
          "TikTok"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "minLength": 2,
                    "description": "Keyword phrase to search on TikTok.",
                    "example": "ai tools"
                  },
                  "max_results": {
                    "type": "integer",
                    "minimum": 0,
                    "default": 0,
                    "description": "Optional upper bound on results the background task will try to collect. `0` (the default) or omitted means **unlimited** \u2014 pagination only stops at TikTok's `has_more=false` or the upstream page cap. With `parallel_search_slices=1` that naturally yields ~89-120 items (TikTok serves ~10 pages of ~12 items per chain); with `parallel_search_slices=4` up to ~480 unique items. A positive integer caps the merged result count. **No hard server-side cap is enforced** \u2014 a caller asking for `3000` will be billed per residential page actually fetched (~10 pages per slice naturally) and receive whatever TikTok delivers."
                  },
                  "parallel_search_slices": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 4,
                    "default": 1,
                    "example": 2,
                    "description": "How many concurrent paginated query chains to run and dedup by video id. `1` (the default) is a single chain naturally capped near 120 items. `2..4` runs that many additional chains in parallel, lifting the effective ceiling at the cost of up to **~Nx the residential pages** of a single search. Diminishing returns: 2 slices yield ~+43% unique items, 3 slices ~+22%, 4 slices ~+13% on a representative query, so 4 already saturates TikTok's natural ~480-item dedup ceiling for most queries. `parallel_search_slices` is *not* a time filter \u2014 use `after_datetime` / `before_datetime` for time windows."
                  },
                  "after_datetime": {
                    "type": "string",
                    "description": "Only include videos published on or after this boundary. Accepts YYYY-MM-DD or ISO datetime with timezone."
                  },
                  "before_datetime": {
                    "type": "string",
                    "description": "Only include videos published on or before this boundary. Accepts YYYY-MM-DD or ISO datetime with timezone."
                  },
                  "min_likes": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "max_likes": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "min_views": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "max_views": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Task accepted and queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "task_id": {
                          "type": "string"
                        },
                        "task_status": {
                          "type": "string",
                          "enum": [
                            "processing"
                          ]
                        },
                        "query": {
                          "type": "string"
                        },
                        "max_results": {
                          "type": "integer"
                        },
                        "parallel_search_slices": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 4,
                          "description": "Echo of the slice count the task was created with."
                        },
                        "filters": {
                          "type": "object"
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "check_status_url": {
                          "type": "string",
                          "example": "/v1/tiktok/search/550e8400-e29b-41d4-a716-446655440000"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/tiktok/search/{task_id}": {
      "get": {
        "summary": "Get TikTok keyword search result",
        "description": "Poll an async TikTok keyword search task and retrieve cursor-paginated normalized result items.\n\nThis endpoint does not consume credits \u2014 polling is free.\n\n**Usage disclosure:** Set `include_usage=true` to receive a `usage` block describing the charges the background worker recorded for this task. Returned only when `task_status=completed` (processing tasks haven't finished billing; failed tasks have all charges refunded). Charges are `pages_fetched \u00d7 residential_request` (no `search_request` is billed for TikTok keyword search).",
        "operationId": "getTikTokSearch",
        "tags": [
          "TikTok"
        ],
        "parameters": [
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "include_usage",
            "in": "query",
            "required": false,
            "description": "When true and `task_status=completed`, attach a `usage` block describing the charges the background worker recorded (`pages_fetched \u00d7 residential_request`; no `search_request` for TikTok keyword search).",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Task found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/TikTokSearchTask"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "Task not found, expired, or not owned by this user."
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/files": {
      "get": {
        "summary": "Get list of uploaded files",
        "description": "Retrieve a paginated list of all files uploaded by the authenticated user. Supports filtering by status and namespace.",
        "operationId": "getFiles",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of files to return",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of files to skip for pagination",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by file status",
            "schema": {
              "type": "string",
              "enum": [
                "processing",
                "completed",
                "failed",
                "cancelled"
              ]
            }
          },
          {
            "name": "namespace_id",
            "in": "query",
            "description": "Filter by namespace ID. Only returns files belonging to this namespace. If not set, returns files from all namespaces.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Files retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "files": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/FileInfo"
                          }
                        },
                        "total_count": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        },
                        "has_more": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/file/{file_id}": {
      "get": {
        "summary": "Get file information and transcript",
        "description": "Retrieve detailed information about a specific file including its transcript if available",
        "operationId": "getFile",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "The ID of the uploaded file",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "transcript_text",
            "in": "query",
            "required": false,
            "description": "When true, returns the transcript as a single plain-text string instead of an array of segments (only when transcript is available).",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File information retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "file_info": {
                          "$ref": "#/components/schemas/FileInfo"
                        },
                        "transcript": {
                          "$ref": "#/components/schemas/TranscriptOutput",
                          "description": "Only included if has_transcript is true"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/AccessDenied"
          },
          "404": {
            "$ref": "#/components/responses/FileNotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/analyze/video": {
      "post": {
        "summary": "Analyze online video",
        "description": "Analyze an online video and return comprehensive analysis results with intelligent caching.\n\n**Behavior:**\n- If no query provided: Returns summary analysis (cached if available)\n- If query provided: Returns both summary (from cache if available) and fresh question analysis\n\n**Caching:**\n- Summary analyses are cached and reused\n- Question/query analyses are not cached (always fresh)\n- Transcripts are cached for optimization\n\n**Billing:** `analysis_request` is token-based \u2014 `quantity = ceil(total_tokens / 15000)`. One unit is charged up front as a credit gate; additional units are topped up at the end if the LLM call exceeded 15,000 tokens. When the transcript isn't cached, one additional `residential_request` or `standard_request` is charged for the fetch (residential for YouTube/Instagram/Facebook-watch/Rumble, standard otherwise). Set `include_usage=true` to receive the per-charge breakdown \u2014 the consolidated `analysis_request` entry carries a nested `tokens` object with `prompt_tokens`/`completion_tokens`/`total_tokens`.\n\nOptional: set `transcript_text=true` to return the transcript as a single text string instead of an array of segments.",
        "operationId": "analyzeVideo",
        "tags": [
          "Analysis"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "video_url"
                ],
                "properties": {
                  "video_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL of the video to analyze",
                    "example": "https://youtube.com/watch?v=dQw4w9WgXcQ"
                  },
                  "query": {
                    "type": "string",
                    "description": "Optional question about the video content",
                    "example": "What is the main topic discussed?"
                  },
                  "transcript_text": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, returns the transcript as a single plain-text string instead of an array of segments."
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, the response includes a `usage` block listing every meter charged during this request, the total credits deducted, and the user's remaining balance."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Video analyzed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "video_info": {
                          "$ref": "#/components/schemas/VideoInfo"
                        },
                        "transcript": {
                          "$ref": "#/components/schemas/TranscriptOutput"
                        },
                        "transcript_analysis": {
                          "$ref": "#/components/schemas/AnalysisResult"
                        }
                      }
                    },
                    "usage": {
                      "$ref": "#/components/schemas/UsageBlock"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "404": {
            "$ref": "#/components/responses/VideoNotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/analyze/file": {
      "post": {
        "summary": "Analyze uploaded file",
        "description": "Analyze an uploaded file and return comprehensive analysis results with intelligent caching.\n\n**Behavior:**\n- If no query provided: Returns summary analysis (cached if available)\n- If query provided: Returns both summary (from cache if available) and fresh question analysis\n\n**Billing:** `analysis_request` is token-based \u2014 `quantity = ceil(total_tokens / 15000)`. One unit is charged up front as a credit gate; additional units are topped up at the end if the LLM call exceeded 15,000 tokens. The file's transcript is read locally from your storage \u2014 no proxy fetch is involved. Set `include_usage=true` to receive the per-charge breakdown.\n\nOptional: set `transcript_text=true` to return the transcript as a single text string instead of an array of segments.",
        "operationId": "analyzeFile",
        "tags": [
          "Analysis"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "file_id"
                ],
                "properties": {
                  "file_id": {
                    "type": "string",
                    "description": "ID of the uploaded file to analyze"
                  },
                  "query": {
                    "type": "string",
                    "description": "Optional question about the file content",
                    "example": "What is discussed in this file?"
                  },
                  "transcript_text": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, returns the transcript as a single plain-text string instead of an array of segments."
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, the response includes a `usage` block listing every meter charged during this request, the total credits deducted, and the user's remaining balance."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File analyzed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "file_info": {
                          "$ref": "#/components/schemas/FileInfo"
                        },
                        "transcript": {
                          "$ref": "#/components/schemas/TranscriptOutput"
                        },
                        "transcript_analysis": {
                          "$ref": "#/components/schemas/AnalysisResult"
                        }
                      }
                    },
                    "usage": {
                      "$ref": "#/components/schemas/UsageBlock"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/AccessDenied"
          },
          "404": {
            "$ref": "#/components/responses/FileNotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/extract/video": {
      "post": {
        "summary": "Extract structured data from online video",
        "description": "Extract structured data from an online video's transcript using a custom schema.\n\nProvide a `video_url` and a `schema` describing the fields to extract. Optionally include `what_to_extract` to guide the extraction.\n\n**Auto-transcription:** For non-YouTube videos without an existing transcript (e.g. Instagram, TikTok, Facebook), the API automatically transcribes the video audio when `transcribe` is `true` (the default). This uses speech-to-text credits (`transcription_hour` usage). YouTube videos rely on platform captions and cannot be auto-transcribed. Set `transcribe=false` to disable this behavior.\n\n**Schema format:** Each field must have `type` and `description`. Supported types: `String`, `Number`, `Boolean`, `Integer`, `Object`, `Array`, `Enum`. Max 10 root fields, max 3 nesting levels.\n\n**Content-Type:** Accepts `application/json`, YAML (`application/yaml`, `application/x-yaml`, `text/yaml`), or `multipart/form-data`. For multipart requests, send `video_url` as a form field and `schema` as either a JSON/YAML form value or an uploaded JSON/YAML file (for example, `schema=@fact-check.yaml`). If the uploaded schema file contains a full request body with a nested `schema` property, the nested schema is used and the form `video_url` takes precedence.\n\n**Token usage:** Set `include_usage=true` to include prompt/completion token counts in the response.\n\n**Billing:** Each extraction consumes at least 1 analysis_request unit. For longer transcripts, billing scales as `ceil(total_tokens / 15000)` analysis_request units. If auto-transcription is triggered, transcription_hour usage is also charged based on video duration. All charges are reverted if the request fails.",
        "operationId": "extractVideoData",
        "tags": [
          "Extraction"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "video_url",
                  "schema"
                ],
                "properties": {
                  "video_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL of the video to extract data from",
                    "example": "https://youtube.com/watch?v=dQw4w9WgXcQ"
                  },
                  "schema": {
                    "$ref": "#/components/schemas/ExtractionSchema"
                  },
                  "what_to_extract": {
                    "type": "string",
                    "description": "Optional guidance for what to extract from the transcript",
                    "example": "Extract the main topics and any product names mentioned"
                  },
                  "transcribe": {
                    "type": "boolean",
                    "default": true,
                    "description": "When true, automatically transcribes the video audio if no platform transcript is available. Applies to non-YouTube videos only (Instagram, TikTok, Facebook, X, etc.). Uses speech-to-text credits based on video duration."
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, includes token usage statistics in the response"
                  }
                }
              }
            },
            "application/yaml": {
              "schema": {
                "type": "object",
                "required": [
                  "video_url",
                  "schema"
                ],
                "properties": {
                  "video_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL of the video to extract data from",
                    "example": "https://youtube.com/watch?v=dQw4w9WgXcQ"
                  },
                  "schema": {
                    "$ref": "#/components/schemas/ExtractionSchema"
                  },
                  "what_to_extract": {
                    "type": "string",
                    "description": "Optional guidance for what to extract from the transcript",
                    "example": "Extract the main topics and any product names mentioned"
                  },
                  "transcribe": {
                    "type": "boolean",
                    "default": true,
                    "description": "When true, automatically transcribes the video audio if no platform transcript is available. Applies to non-YouTube videos only (Instagram, TikTok, Facebook, X, etc.). Uses speech-to-text credits based on video duration."
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, includes token usage statistics in the response"
                  }
                }
              }
            },
            "application/x-yaml": {
              "schema": {
                "type": "object",
                "required": [
                  "video_url",
                  "schema"
                ],
                "properties": {
                  "video_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL of the video to extract data from",
                    "example": "https://youtube.com/watch?v=dQw4w9WgXcQ"
                  },
                  "schema": {
                    "$ref": "#/components/schemas/ExtractionSchema"
                  },
                  "what_to_extract": {
                    "type": "string",
                    "description": "Optional guidance for what to extract from the transcript",
                    "example": "Extract the main topics and any product names mentioned"
                  },
                  "transcribe": {
                    "type": "boolean",
                    "default": true,
                    "description": "When true, automatically transcribes the video audio if no platform transcript is available. Applies to non-YouTube videos only (Instagram, TikTok, Facebook, X, etc.). Uses speech-to-text credits based on video duration."
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, includes token usage statistics in the response"
                  }
                }
              }
            },
            "text/yaml": {
              "schema": {
                "type": "object",
                "required": [
                  "video_url",
                  "schema"
                ],
                "properties": {
                  "video_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL of the video to extract data from",
                    "example": "https://youtube.com/watch?v=dQw4w9WgXcQ"
                  },
                  "schema": {
                    "$ref": "#/components/schemas/ExtractionSchema"
                  },
                  "what_to_extract": {
                    "type": "string",
                    "description": "Optional guidance for what to extract from the transcript",
                    "example": "Extract the main topics and any product names mentioned"
                  },
                  "transcribe": {
                    "type": "boolean",
                    "default": true,
                    "description": "When true, automatically transcribes the video audio if no platform transcript is available. Applies to non-YouTube videos only (Instagram, TikTok, Facebook, X, etc.). Uses speech-to-text credits based on video duration."
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, includes token usage statistics in the response"
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "video_url",
                  "schema"
                ],
                "properties": {
                  "video_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL of the video to extract data from",
                    "example": "https://youtube.com/watch?v=dQw4w9WgXcQ"
                  },
                  "schema": {
                    "type": "string",
                    "format": "binary",
                    "description": "JSON or YAML schema file. The file may contain either the schema object directly or a full extraction request object with a nested `schema` property."
                  },
                  "what_to_extract": {
                    "type": "string",
                    "description": "Optional guidance for what to extract from the transcript",
                    "example": "Extract the main topics and any product names mentioned"
                  },
                  "transcribe": {
                    "type": "boolean",
                    "default": true,
                    "description": "When true, automatically transcribes the video audio if no platform transcript is available. Applies to non-YouTube videos only (Instagram, TikTok, Facebook, X, etc.). Uses speech-to-text credits based on video duration."
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, includes token usage statistics in the response"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Data extracted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing parameters, invalid schema, or input too large",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "missing_parameter",
                        "request_body_required",
                        "invalid_schema",
                        "input_too_large"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "404": {
            "description": "Video or transcript not found. Returned when no transcript is available and auto-transcription is disabled or not supported (YouTube).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "video_not_found",
                        "transcript_not_available"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error, including transcription failures",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal_server_error",
                        "extraction_failed",
                        "transcription_failed"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/SystemOverload"
          }
        }
      }
    },
    "/extract/file": {
      "post": {
        "summary": "Extract structured data from uploaded file",
        "description": "Extract structured data from an uploaded file's transcript using a custom schema.\n\nProvide a `file_id` and a `schema` describing the fields to extract. The file must be processed and have a transcript available. Optionally include `what_to_extract` to guide the extraction.\n\n**Schema format:** Each field must have `type` and `description`. Supported types: `String`, `Number`, `Boolean`, `Integer`, `Object`, `Array`, `Enum`. Max 10 root fields, max 3 nesting levels.\n\n**Content-Type:** Accepts `application/json`, YAML (`application/yaml`, `application/x-yaml`, `text/yaml`), or `multipart/form-data`. For multipart requests, send `file_id` as a form field and `schema` as either a JSON/YAML form value or an uploaded JSON/YAML file. If the uploaded schema file contains a full request body with a nested `schema` property, the nested schema is used and the form `file_id` takes precedence.\n\n**Token usage:** Set `include_usage=true` to include prompt/completion token counts in the response.\n\n**Billing:** Each extraction consumes at least 1 analysis credit. For longer transcripts, billing scales as `ceil(total_tokens / 15000)` credits. All charges are reverted if the request fails.",
        "operationId": "extractFileData",
        "tags": [
          "Extraction"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "file_id",
                  "schema"
                ],
                "properties": {
                  "file_id": {
                    "type": "string",
                    "description": "ID of the uploaded file to extract data from"
                  },
                  "schema": {
                    "$ref": "#/components/schemas/ExtractionSchema"
                  },
                  "what_to_extract": {
                    "type": "string",
                    "description": "Optional guidance for what to extract from the transcript",
                    "example": "Extract action items and deadlines from this meeting"
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, includes token usage statistics in the response"
                  }
                }
              }
            },
            "application/yaml": {
              "schema": {
                "type": "object",
                "required": [
                  "file_id",
                  "schema"
                ],
                "properties": {
                  "file_id": {
                    "type": "string",
                    "description": "ID of the uploaded file to extract data from"
                  },
                  "schema": {
                    "$ref": "#/components/schemas/ExtractionSchema"
                  },
                  "what_to_extract": {
                    "type": "string",
                    "description": "Optional guidance for what to extract from the transcript",
                    "example": "Extract action items and deadlines from this meeting"
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, includes token usage statistics in the response"
                  }
                }
              }
            },
            "application/x-yaml": {
              "schema": {
                "type": "object",
                "required": [
                  "file_id",
                  "schema"
                ],
                "properties": {
                  "file_id": {
                    "type": "string",
                    "description": "ID of the uploaded file to extract data from"
                  },
                  "schema": {
                    "$ref": "#/components/schemas/ExtractionSchema"
                  },
                  "what_to_extract": {
                    "type": "string",
                    "description": "Optional guidance for what to extract from the transcript",
                    "example": "Extract action items and deadlines from this meeting"
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, includes token usage statistics in the response"
                  }
                }
              }
            },
            "text/yaml": {
              "schema": {
                "type": "object",
                "required": [
                  "file_id",
                  "schema"
                ],
                "properties": {
                  "file_id": {
                    "type": "string",
                    "description": "ID of the uploaded file to extract data from"
                  },
                  "schema": {
                    "$ref": "#/components/schemas/ExtractionSchema"
                  },
                  "what_to_extract": {
                    "type": "string",
                    "description": "Optional guidance for what to extract from the transcript",
                    "example": "Extract action items and deadlines from this meeting"
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, includes token usage statistics in the response"
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file_id",
                  "schema"
                ],
                "properties": {
                  "file_id": {
                    "type": "string",
                    "description": "ID of the uploaded file to extract data from"
                  },
                  "schema": {
                    "type": "string",
                    "format": "binary",
                    "description": "JSON or YAML schema file. The file may contain either the schema object directly or a full extraction request object with a nested `schema` property."
                  },
                  "what_to_extract": {
                    "type": "string",
                    "description": "Optional guidance for what to extract from the transcript",
                    "example": "Extract action items and deadlines from this meeting"
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, includes token usage statistics in the response"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Data extracted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing parameters, invalid schema, or input too large",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "missing_parameter",
                        "request_body_required",
                        "invalid_schema",
                        "input_too_large"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/AccessDenied"
          },
          "404": {
            "description": "File not found or transcript not available",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "file_not_found",
                        "transcript_not_available"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/SystemOverload"
          }
        }
      }
    },
    "/youtube/search": {
      "post": {
        "summary": "Search YouTube for videos",
        "description": "Search YouTube for videos using AI analysis and ranking with optional filters.\n\n**Process:**\n1. Enhanced search with prompt engineering to find relevant videos\n2. AI analysis of video content and transcripts (per-video residential proxy fetch + per-video LLM analysis)\n3. Intelligent ranking based on query relevance\n4. Structured results with rich metadata\n\n**Billing:** for each candidate video the API bills one `residential_request` when fetching the transcript \u2014 set `max_results` to cap how many candidates are processed and therefore how many residential charges fire. After all per-video AI analyses AND the ranking step complete, a single consolidated `analysis_request` charge is recorded with `quantity = ceil(total_tokens / 15000)`, where `total_tokens` is the sum of LLM input + output tokens across all videos analyzed plus the ranking call. No `search_request` is billed \u2014 the residential fetches + analysis fully cover the workflow. If the search returns zero results, nothing is billed. Set `include_usage=true` to receive the full per-charge breakdown \u2014 the consolidated `analysis_request` entry also carries a nested `tokens` object reporting `prompt_tokens` / `completion_tokens` / `total_tokens`.\n\n**Partial results:** if the user runs out of `residential_request` credits mid-search, the endpoint returns HTTP `402` with `status: \"partial\"`, the videos analyzed so far in `data.results`, and `error_code: \"insufficient_credits_video_search\"`. The `usage` block (when `include_usage=true`) reflects only the charges that succeeded.",
        "operationId": "searchYouTube",
        "tags": [
          "Search"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "Search query",
                    "example": "What are the best practices for React development?"
                  },
                  "use_enhanced_search": {
                    "type": "boolean",
                    "default": true,
                    "description": "Whether to use enhanced search"
                  },
                  "start_year": {
                    "type": "integer",
                    "description": "Filter by start year",
                    "example": 2020
                  },
                  "end_year": {
                    "type": "integer",
                    "description": "Filter by end year",
                    "example": 2024
                  },
                  "focus": {
                    "type": "string",
                    "enum": [
                      "relevance",
                      "popularity",
                      "brevity"
                    ],
                    "default": "relevance",
                    "description": "Search focus"
                  },
                  "duration": {
                    "type": "integer",
                    "description": "Maximum duration in seconds",
                    "example": 600
                  },
                  "max_results": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Maximum number of videos to analyse and return. Each candidate triggers one `residential_request` (transcript fetch) and contributes tokens to the consolidated `analysis_request` charge, so lowering this caps the per-call cost. When omitted, defaults to the user's plan ceiling (`videos_per_search_count`). Values above the plan ceiling are silently clamped down.",
                    "example": 2
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, the response includes a `usage` block listing every meter charged during this request, the total credits deducted, and the user's remaining balance."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search completed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "results": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/VideoSearchResult"
                          }
                        },
                        "query": {
                          "type": "string"
                        },
                        "total_found": {
                          "type": "integer"
                        },
                        "explanation": {
                          "type": "string"
                        }
                      }
                    },
                    "usage": {
                      "$ref": "#/components/schemas/UsageBlock"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "402": {
            "description": "Either the initial search request was denied for insufficient credits (`status: error`), or credits ran out mid-analysis after some videos were already processed (`status: partial`, partial results included).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/PaymentRequiredBody"
                    },
                    {
                      "type": "object",
                      "description": "Partial-results 402 \u2014 some videos were analyzed before credits ran out.",
                      "properties": {
                        "status": {
                          "type": "string",
                          "enum": [
                            "partial"
                          ]
                        },
                        "error_code": {
                          "type": "string",
                          "enum": [
                            "insufficient_credits_video_search"
                          ]
                        },
                        "message": {
                          "type": "string"
                        },
                        "data": {
                          "type": "object",
                          "properties": {
                            "results": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/VideoSearchResult"
                              }
                            },
                            "query": {
                              "type": "string"
                            },
                            "total_found": {
                              "type": "integer"
                            },
                            "explanation": {
                              "type": "string"
                            }
                          }
                        },
                        "usage": {
                          "$ref": "#/components/schemas/UsageBlock"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/search/file": {
      "post": {
        "summary": "Search uploaded files",
        "description": "Search through user's uploaded files using vector similarity with AI reranking.\n\n**Namespace filtering:** Pass `namespace_ids` to restrict the search to files in specific namespaces. If omitted, all namespaces are searched.\n\n**Process:**\n1. Vector similarity search using text embeddings\n2. Initial ranking by semantic similarity\n3. AI reranking for improved relevance\n4. Rich metadata and signed URLs for file access\n5. Optional per-file AI analysis for people, places, key subjects, and key moments\n\nEach result includes the `namespace_ids` and resolved `namespaces` the file belongs to.\n\n**Billing:** one `search_request` per semantic search over this already indexed content; 1 credit covers 1,000 semantic searches. The AI analysis pass (per-file + overall aggregation) bills `analysis_request` as `ceil(total_tokens / 15000)` units (minimum 1 when analysis ran). Set `include_usage=true` to receive the per-charge breakdown \u2014 the consolidated `analysis_request` entry carries a nested `tokens` object.",
        "operationId": "searchFiles",
        "tags": [
          "Search"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "Search query",
                    "example": "What did the customer say about pricing?"
                  },
                  "namespace_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Optional list of namespace IDs to restrict search scope. If not provided, all namespaces are searched."
                  },
                  "include_usage": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, the response includes a `usage` block listing every meter charged during this request, the total credits deducted, and the user's remaining balance."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search completed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "results": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/FileSearchResult"
                          }
                        },
                        "query": {
                          "type": "string"
                        },
                        "total_found": {
                          "type": "integer"
                        },
                        "explanation": {
                          "type": "string"
                        }
                      }
                    },
                    "usage": {
                      "$ref": "#/components/schemas/UsageBlock"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/namespaces": {
      "get": {
        "summary": "List namespaces",
        "description": "Get all namespaces for the authenticated user. A 'default' namespace is auto-created if none exist. Files without a namespace are lazily migrated to the default.",
        "operationId": "getNamespaces",
        "tags": [
          "Namespaces"
        ],
        "responses": {
          "200": {
            "description": "Namespaces retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Namespace"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "summary": "Create a namespace",
        "description": "Create a new namespace for organizing files. Maximum 5 namespaces per user. The name 'default' is reserved and cannot be used.",
        "operationId": "createNamespace",
        "tags": [
          "Namespaces"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Name for the new namespace (cannot be 'default')",
                    "example": "Client Calls"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Namespace created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/Namespace"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/namespaces/{namespace_id}": {
      "put": {
        "summary": "Rename a namespace",
        "description": "Rename an existing namespace. The 'default' namespace cannot be renamed.",
        "operationId": "updateNamespace",
        "tags": [
          "Namespaces"
        ],
        "parameters": [
          {
            "name": "namespace_id",
            "in": "path",
            "required": true,
            "description": "The ID of the namespace to rename",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "New name for the namespace",
                    "example": "Product Demos"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Namespace renamed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "delete": {
        "summary": "Delete a namespace",
        "description": "Delete a namespace and remove its association from all files. The 'default' namespace cannot be deleted.",
        "operationId": "deleteNamespace",
        "tags": [
          "Namespaces"
        ],
        "parameters": [
          {
            "name": "namespace_id",
            "in": "path",
            "required": true,
            "description": "The ID of the namespace to delete",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Namespace deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "Namespace not found or could not be deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/file/{file_id}/namespaces": {
      "put": {
        "summary": "Update file namespace assignments",
        "description": "Set the namespaces a file belongs to. Replaces any existing namespace assignments. The 'default' namespace is always preserved.",
        "operationId": "updateFileNamespaces",
        "tags": [
          "Namespaces"
        ],
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "The ID of the file to update",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "namespace_ids"
                ],
                "properties": {
                  "namespace_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "List of namespace IDs to assign to this file",
                    "example": [
                      "64a1b2c3d4e5f6789..."
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File namespaces updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "namespace_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Updated list of namespace IDs"
                        },
                        "namespaces": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/NamespaceRef"
                          },
                          "description": "Resolved namespaces with names"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/FileNotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/upload/file": {
      "post": {
        "summary": "Upload a file for analysis",
        "description": "Upload a new audio or video file for analysis and start processing.\n\n**Supported formats:**\n- Video: mp4, webm, mov, avi, wmv, flv, mkv\n- Audio: m4a, mp3, mpeg, mpga, wav\n\n**Processing options:**\n- `wait_for_completion=false` (default): Returns immediately, processing happens in background\n- `wait_for_completion=true`: Waits for complete processing before returning response",
        "operationId": "uploadFile",
        "tags": [
          "Files"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The audio or video file to upload"
                  },
                  "wait_for_completion": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false",
                      "1",
                      "0",
                      "yes",
                      "no",
                      "y",
                      "n"
                    ],
                    "default": "false",
                    "description": "If 'true', waits until processing is complete before returning response"
                  },
                  "namespace_ids": {
                    "type": "string",
                    "description": "Optional namespace IDs to assign the file to. Accepts a comma-separated string or a JSON array (e.g., '[\"ns1\",\"ns2\"]')."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "File uploaded successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "file_id": {
                      "type": "string"
                    },
                    "file_name": {
                      "type": "string"
                    },
                    "file_status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "completed"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "file_info": {
                          "$ref": "#/components/schemas/FileInfo"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "File uploaded but processing timed out (continues in background). Returned when wait_for_completion=true and processing exceeds 15 minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "accepted"
                      ]
                    },
                    "file_id": {
                      "type": "string"
                    },
                    "file_name": {
                      "type": "string"
                    },
                    "file_status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "file_info": {
                          "$ref": "#/components/schemas/FileInfo"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "413": {
            "$ref": "#/components/responses/StorageQuotaExceeded"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/StorageNotConfigured"
          }
        }
      }
    },
    "/file/{file_id}/retry": {
      "post": {
        "summary": "Retry failed file processing",
        "description": "Retry processing for a file that previously failed or was cancelled",
        "operationId": "retryFileProcessing",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "The ID of the file to retry",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File processing restarted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "file_id": {
                          "type": "string"
                        },
                        "file_name": {
                          "type": "string"
                        },
                        "file_status": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/AccessDenied"
          },
          "404": {
            "$ref": "#/components/responses/FileNotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/file/{file_id}/cancel": {
      "post": {
        "summary": "Cancel file processing",
        "description": "Cancel an ongoing file upload/processing",
        "operationId": "cancelFileUpload",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "The ID of the file to cancel",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File cancelled successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "file_id": {
                          "type": "string"
                        },
                        "file_name": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/AccessDenied"
          },
          "404": {
            "$ref": "#/components/responses/FileNotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/file/{file_id}/delete": {
      "delete": {
        "summary": "Delete a file",
        "description": "Delete a file and its associated data from the database and cloud storage",
        "operationId": "deleteFile",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "The ID of the file to delete",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "file_id": {
                          "type": "string"
                        },
                        "file_name": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/AccessDenied"
          },
          "404": {
            "$ref": "#/components/responses/FileNotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/file/{file_id}/url": {
      "get": {
        "summary": "Get download URL for a file",
        "description": "Returns a short-lived (~1 hour) signed Google Cloud Storage URL pointing directly at the file bytes. Same model as AWS S3 / CloudFront pre-signed URLs: the URL itself is the capability, time-limited and resource-scoped, so it can be used from browsers, no-code tools, or any HTTP client without forwarding header auth. Call this endpoint again at any time to mint a fresh URL.",
        "operationId": "getFileUrl",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "name": "file_id",
            "in": "path",
            "required": true,
            "description": "The ID of the file",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File URL generated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "file_id": {
                          "type": "string"
                        },
                        "file_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "Short-lived signed Google Cloud Storage URL with the file bytes. Valid for ~1 hour.",
                          "example": "https://storage.googleapis.com/bucket/path/to/file?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Signature=..."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/AccessDenied"
          },
          "404": {
            "$ref": "#/components/responses/FileNotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "summary": "Get usage statistics",
        "description": "Retrieve credit balances, per-service activity counts, storage, and channels indexed for the authenticated user. All API operations consume credits from a shared pool \u2014 there are no per-service limits.",
        "operationId": "getUsage",
        "tags": [
          "System"
        ],
        "responses": {
          "200": {
            "description": "Usage statistics retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/UsageData"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No active subscription found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "status": "error",
                  "error": "no_subscription",
                  "message": "You are not subscribed to any plan."
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/AccessDenied"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/health": {
      "get": {
        "summary": "Health check",
        "description": "Check API health and get endpoint information",
        "operationId": "healthCheck",
        "tags": [
          "System"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "API is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "version": {
                      "type": "string"
                    },
                    "endpoints": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "path": {
                            "type": "string"
                          },
                          "method": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "auth_required": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/tweet/statement": {
      "post": {
        "summary": "Extract structured claim from an X/Twitter tweet",
        "description": "Fetches the tweet content (including any quoted tweet and attached media), then uses AI to extract a rich structured analysis: the core claim, classification axes, topics, entities, and raw tweet data.\n\n**Multimodal media handling:** attached media is processed by type. Videos are transcribed (platform captions or speech-to-text) and summarized; images are described by a vision model — transcribed text, chart/graph data points, recognisable people and logos, and any claims they make — and folded into the analysis.\n\n**Classification axes** (`claim_type`, `intent`, `tone`, `emotion`, `authority`) reflect **only the original tweet text**, not quoted content, media, or images.\n\n**Billing (consolidated, mirrors `/v1/analyze/video`):**\n- exactly one `analysis_request` charge with `quantity = ceil(total_tokens / 15000)`, summing tokens across the optional per-media extraction calls, the optional image vision calls, AND the main statement-extraction call. A typical tweet+quoted tweet with one video or an attached image runs several LLM calls but produces just one billing entry when the combined tokens stay under 15k. Image analysis is billed through this same `analysis_request` meter.\n- one `standard_request` per media URL whose transcript or metadata is fetched (mirrors `/v1/transcript`).\n- `transcription_hour` proportional to the audio duration if speech-to-text runs for a media video that has no platform transcript. Waived for users with `users.free_on_cache_hit=true` when STT was served from cache (same waiver as `/v1/transcribe`).",
        "operationId": "getTweetStatement",
        "tags": [
          "Tweet Analysis"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tweet_id"
                ],
                "properties": {
                  "tweet_id": {
                    "type": "string",
                    "description": "The X/Twitter tweet ID (numeric string)",
                    "example": "1234567890123456789"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful extraction",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "final_statement": {
                          "type": "string",
                          "description": "Complete nuanced claim in the author's voice (multi-sentence)"
                        },
                        "detailed_analysis": {
                          "type": "string",
                          "description": "3-5 sentence breakdown: claim, context, evidence, stance"
                        },
                        "topics": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "3-8 key topics or subjects"
                        },
                        "entities": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "0-8 proper nouns (people, organisations, places)"
                        },
                        "claim_type": {
                          "type": "string",
                          "enum": [
                            "factual_claim",
                            "opinion",
                            "question",
                            "call_to_action",
                            "satire",
                            "news_sharing",
                            "personal_experience"
                          ],
                          "description": "Primary classification of the claim (based on original tweet text only)"
                        },
                        "intent": {
                          "type": "string",
                          "enum": [
                            "educate",
                            "inform",
                            "analyze",
                            "persuade",
                            "entertain",
                            "inspire",
                            "challenge"
                          ],
                          "description": "Primary intent of the tweet author (based on original tweet text only)"
                        },
                        "tone": {
                          "type": "string",
                          "enum": [
                            "serious",
                            "humorous",
                            "provocative",
                            "neutral",
                            "warm",
                            "skeptical",
                            "inspirational"
                          ],
                          "description": "Dominant tone of the tweet (based on original tweet text only)"
                        },
                        "emotion": {
                          "type": "string",
                          "enum": [
                            "curiosity",
                            "urgency",
                            "outrage",
                            "fear",
                            "hope_inspiration",
                            "confidence_reassurance",
                            "empathy_warmth",
                            "awe_wonder"
                          ],
                          "description": "Primary emotion evoked by the tweet (based on original tweet text only)"
                        },
                        "authority": {
                          "type": "string",
                          "enum": [
                            "data_driven",
                            "expert_led",
                            "experience_based",
                            "speculative"
                          ],
                          "description": "Type of authority backing the claim (based on original tweet text only)"
                        },
                        "tweet_text": {
                          "type": "string",
                          "nullable": true,
                          "description": "The original tweet text"
                        },
                        "tweet_media_summary": {
                          "type": "string",
                          "nullable": true,
                          "description": "Full content summary of media attached to the original tweet: a video transcript summary, OR a vision-model description of attached image(s) (transcribed text, chart data, people/logos, claims). Null when no media is attached."
                        },
                        "quoted_tweet_text": {
                          "type": "string",
                          "nullable": true,
                          "description": "Text of the quoted/referenced tweet, if any"
                        },
                        "quoted_media_summary": {
                          "type": "string",
                          "nullable": true,
                          "description": "Full content summary of media attached to the quoted tweet (video transcript or image description). Null when no media is attached."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid tweet_id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "502": {
            "description": "Failed to fetch tweet from X API or AI could not extract a statement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "error"
                      ]
                    },
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key authentication. Include your VidNavigator API key in the X-API-Key header."
      }
    },
    "schemas": {
      "UsageBlock": {
        "type": "object",
        "description": "Per-call usage disclosure. Returned only when the caller passes `include_usage=true` in the request body. Lists every meter that fired during this request and the credits actually deducted. Multiple charges of the same meter inside one request are consolidated into a single entry (their quantities and credits are summed). When a charge was waived through a cache-hit sponsorship (e.g. NGO), it carries `waived: true` + `credits_saved`, and a top-level `waived.credits_saved` summary appears.\n\nFor endpoints that involve LLM analysis (`/extract/video`, `/extract/file`, `/analyze/video`, `/analyze/file`, `/youtube/search`), the consolidated `analysis_request` charge entry carries a nested `tokens` object reporting the LLM input/output token tally for the request.",
        "properties": {
          "charges": {
            "type": "array",
            "description": "Every billed meter for this request, consolidated: one entry per `service_type`. Multiple billings of the same meter inside the request sum into a single entry. Rollbacks net against the matching charge \u2014 if a charge is fully refunded inside the request, the entry is dropped from the response entirely.",
            "items": {
              "type": "object",
              "properties": {
                "service_type": {
                  "type": "string",
                  "enum": [
                    "standard_request",
                    "residential_request",
                    "transcription_hour",
                    "analysis_request",
                    "search_request",
                    "scene_analysis_hour"
                  ],
                  "description": "Which meter fired."
                },
                "quantity": {
                  "type": "number",
                  "description": "Net units consumed. For `analysis_request`, this is computed as `ceil(total_tokens / 15000)`. For `transcription_hour` / `scene_analysis_hour`, it's in hours."
                },
                "credits": {
                  "type": "number",
                  "description": "Net credits actually deducted for this meter. Will be 0 when the charge was waived."
                },
                "waived": {
                  "type": "boolean",
                  "description": "True when these charges were waived (cache hit + user has free_on_cache_hit sponsorship)."
                },
                "credits_saved": {
                  "type": "number",
                  "description": "When waived=true, the credits that would have been charged otherwise."
                },
                "tokens": {
                  "type": "object",
                  "description": "Only present on the `analysis_request` entry for endpoints that run LLM analysis. Reports the cumulative LLM token tally for this request (sum across all internal AI calls).",
                  "properties": {
                    "prompt_tokens": {
                      "type": "integer",
                      "description": "Total LLM input tokens."
                    },
                    "completion_tokens": {
                      "type": "integer",
                      "description": "Total LLM output tokens."
                    },
                    "total_tokens": {
                      "type": "integer",
                      "description": "Sum of `prompt_tokens` + `completion_tokens`."
                    }
                  }
                }
              }
            }
          },
          "total_credits": {
            "type": "number",
            "description": "Net credits deducted by this request (sum of all `charges[].credits`)."
          },
          "waived": {
            "type": "object",
            "description": "Present only when one or more charges in this request were waived.",
            "properties": {
              "credits_saved": {
                "type": "number",
                "description": "Total credits saved via cache-hit sponsorship on this request."
              }
            }
          }
        }
      },
      "PaymentRequiredBody": {
        "type": "object",
        "description": "Standard 402 error body returned when the user has insufficient credits.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "type": "string",
            "enum": [
              "limit_exceeded"
            ]
          },
          "error_code": {
            "type": "string",
            "enum": [
              "limit_exceeded"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "UsageData": {
        "type": "object",
        "description": "Usage statistics for the authenticated user. All API operations consume credits from a shared pool \u2014 there are no per-service limits.",
        "properties": {
          "usage_period": {
            "type": "object",
            "description": "Monthly usage window (credits reset each period)",
            "properties": {
              "start": {
                "type": "string",
                "format": "date-time"
              },
              "end": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "billing_period": {
            "type": "object",
            "description": "Actual subscription billing window (Stripe period)",
            "properties": {
              "start": {
                "type": "string",
                "format": "date-time"
              },
              "end": {
                "type": "string",
                "format": "date-time"
              },
              "interval": {
                "type": "string",
                "enum": [
                  "month",
                  "year"
                ]
              }
            }
          },
          "subscription": {
            "type": "object",
            "properties": {
              "plan_id": {
                "type": "string"
              },
              "plan_name": {
                "type": "string"
              },
              "interval": {
                "type": "string",
                "enum": [
                  "month",
                  "year"
                ]
              },
              "status": {
                "type": "string"
              },
              "cancel_at_period_end": {
                "type": "boolean"
              }
            }
          },
          "credits": {
            "$ref": "#/components/schemas/CreditsInfo"
          },
          "usage": {
            "type": "object",
            "description": "Activity counters for each service type during the current period. These are informational \u2014 the shared credit pool is what limits usage, not per-service caps.",
            "properties": {
              "standard_request": {
                "$ref": "#/components/schemas/ActivityCount"
              },
              "residential_request": {
                "$ref": "#/components/schemas/ActivityCount"
              },
              "search_request": {
                "$ref": "#/components/schemas/ActivityCount"
              },
              "analysis_request": {
                "$ref": "#/components/schemas/ActivityCount"
              },
              "scene_analysis_hour": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ActivityCount"
                  }
                ],
                "description": "Video scene analysis usage in hours"
              },
              "transcription_hour": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ActivityCount"
                  }
                ],
                "description": "Speech-to-text usage in hours"
              }
            }
          },
          "channels_indexed": {
            "$ref": "#/components/schemas/CapacityMetric",
            "description": "Number of non-public YouTube channels indexed by this user. Hard plan limit \u2014 not credit-gated."
          },
          "storage": {
            "$ref": "#/components/schemas/StorageUsage"
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreditsInfo": {
        "type": "object",
        "description": "Shared credit pool that gates all API operations. Monthly credits reset each billing period; purchased credits persist.",
        "properties": {
          "monthly_total": {
            "oneOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "enum": [
                  "unlimited"
                ]
              }
            ],
            "description": "Total monthly credits included in the plan"
          },
          "monthly_remaining": {
            "oneOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "enum": [
                  "unlimited"
                ]
              }
            ],
            "description": "Monthly credits remaining for the current period"
          },
          "purchased": {
            "type": "number",
            "description": "Additional purchased credits (persist across periods)"
          }
        },
        "required": [
          "monthly_total",
          "monthly_remaining",
          "purchased"
        ]
      },
      "ActivityCount": {
        "type": "object",
        "description": "Activity counter for a credit-gated service. No per-service limit \u2014 usage is gated by the shared credit pool.",
        "properties": {
          "used": {
            "type": "number",
            "description": "Number of times this service was used during the current period"
          },
          "unit": {
            "type": "string",
            "description": "Unit of measurement when not a simple count (e.g., 'hours' for video uploads). Omitted for count-based metrics."
          }
        },
        "required": [
          "used"
        ]
      },
      "CapacityMetric": {
        "type": "object",
        "description": "A capacity metric with a hard plan limit (not credit-gated).",
        "properties": {
          "used": {
            "type": "integer",
            "description": "Current count"
          },
          "limit": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string",
                "enum": [
                  "unlimited"
                ]
              }
            ],
            "description": "Plan limit"
          },
          "remaining": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string",
                "enum": [
                  "unlimited"
                ]
              }
            ],
            "description": "Remaining capacity"
          },
          "percentage": {
            "type": "number",
            "description": "Usage percentage (0-100)"
          }
        },
        "required": [
          "used",
          "limit",
          "remaining",
          "percentage"
        ]
      },
      "StorageUsage": {
        "type": "object",
        "properties": {
          "used_bytes": {
            "type": "integer"
          },
          "used_formatted": {
            "type": "string"
          },
          "limit_bytes": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string",
                "enum": [
                  "unlimited"
                ]
              }
            ]
          },
          "limit_formatted": {
            "type": "string"
          },
          "remaining_bytes": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string",
                "enum": [
                  "unlimited"
                ]
              }
            ]
          },
          "remaining_formatted": {
            "type": "string"
          },
          "percentage": {
            "type": "number"
          }
        }
      },
      "VideoInfo": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Video title"
          },
          "description": {
            "type": "string",
            "description": "Video description"
          },
          "thumbnail": {
            "type": "string",
            "format": "uri",
            "description": "Video thumbnail URL"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Video URL"
          },
          "channel": {
            "type": "string",
            "description": "Channel name"
          },
          "channel_url": {
            "type": "string",
            "format": "uri",
            "description": "Channel URL"
          },
          "duration": {
            "type": "number",
            "description": "Duration in seconds"
          },
          "views": {
            "type": "integer",
            "description": "View count"
          },
          "likes": {
            "type": "integer",
            "description": "Like count"
          },
          "published_date": {
            "type": "string",
            "description": "Publication date"
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Video keywords/tags"
          },
          "category": {
            "type": "string",
            "description": "Video category"
          },
          "available_languages": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Available transcript languages"
          },
          "selected_language": {
            "type": "string",
            "description": "Selected transcript language"
          },
          "carousel_info": {
            "$ref": "#/components/schemas/VideoCarouselInfo",
            "description": "Present only for carousel posts (e.g., Instagram posts with multiple videos)"
          }
        }
      },
      "VideoCarouselInfo": {
        "type": "object",
        "description": "Carousel information for a single video within a carousel post",
        "properties": {
          "total_items": {
            "type": "integer",
            "description": "Total number of items in the carousel (videos + images)"
          },
          "video_count": {
            "type": "integer",
            "description": "Number of videos in the carousel"
          },
          "image_count": {
            "type": "integer",
            "description": "Number of images in the carousel"
          },
          "selected_index": {
            "type": "integer",
            "description": "1-based index of the selected video"
          }
        }
      },
      "CarouselInfo": {
        "type": "object",
        "description": "Summary information for all_videos=true response",
        "properties": {
          "total_items": {
            "type": "integer",
            "description": "Total number of items in the carousel"
          },
          "video_count": {
            "type": "integer",
            "description": "Number of videos in the carousel"
          },
          "image_count": {
            "type": "integer",
            "description": "Number of images in the carousel"
          },
          "transcribed_count": {
            "type": "integer",
            "description": "Number of videos successfully transcribed"
          },
          "total_duration": {
            "type": "number",
            "description": "Total duration of all transcribed videos in seconds"
          }
        }
      },
      "CarouselVideoResult": {
        "type": "object",
        "description": "Result for a single video in an all_videos response",
        "properties": {
          "index": {
            "type": "integer",
            "description": "1-based index of this video in the carousel"
          },
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ],
            "description": "Whether this video was transcribed successfully"
          },
          "video_info": {
            "$ref": "#/components/schemas/VideoInfo",
            "description": "Video metadata (present when status is success)"
          },
          "transcript": {
            "$ref": "#/components/schemas/TranscriptOutput",
            "description": "Video transcript (present when status is success)"
          },
          "error": {
            "type": "string",
            "description": "Error code (present when status is error)"
          },
          "message": {
            "type": "string",
            "description": "Error message (present when status is error)"
          }
        }
      },
      "NamespaceRef": {
        "type": "object",
        "description": "Resolved namespace reference with ID and display name",
        "properties": {
          "id": {
            "type": "string",
            "description": "Namespace identifier"
          },
          "name": {
            "type": "string",
            "description": "Namespace display name"
          }
        }
      },
      "FileInfo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique file identifier"
          },
          "name": {
            "type": "string",
            "description": "File name"
          },
          "size": {
            "type": "integer",
            "description": "File size in bytes"
          },
          "type": {
            "type": "string",
            "description": "MIME type"
          },
          "duration": {
            "type": "number",
            "description": "Duration in seconds"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "completed",
              "failed",
              "cancelled"
            ],
            "description": "Processing status"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Upload timestamp"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Last update timestamp"
          },
          "original_file_date": {
            "type": "string",
            "format": "date-time",
            "description": "Original file creation date"
          },
          "has_transcript": {
            "type": "boolean",
            "description": "Whether transcript is available"
          },
          "error_message": {
            "type": "string",
            "description": "Error message if processing failed"
          },
          "namespace_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "IDs of the namespaces this file belongs to"
          },
          "namespaces": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NamespaceRef"
            },
            "description": "Resolved namespaces this file belongs to (with names)"
          }
        }
      },
      "TranscriptSegment": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "description": "Transcript text for this segment"
          },
          "start": {
            "type": "number",
            "description": "Start time in seconds"
          },
          "end": {
            "type": "number",
            "description": "End time in seconds"
          }
        }
      },
      "TranscriptOutput": {
        "description": "Transcript output. By default it is an array of segments. When `transcript_text=true`, it is returned as a single plain-text string.",
        "oneOf": [
          {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TranscriptSegment"
            }
          },
          {
            "type": "string"
          }
        ]
      },
      "AnalysisResult": {
        "type": "object",
        "properties": {
          "summary": {
            "type": "string",
            "description": "Content summary"
          },
          "people": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "context": {
                  "type": "string"
                }
              }
            },
            "description": "People mentioned in the content"
          },
          "places": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "context": {
                  "type": "string"
                }
              }
            },
            "description": "Places mentioned in the content"
          },
          "key_subjects": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "importance": {
                  "type": "string"
                }
              }
            },
            "description": "Key subjects/topics discussed"
          },
          "timestamp": {
            "type": "number",
            "description": "Key moment timestamp (if applicable)"
          },
          "relevant_text": {
            "type": "string",
            "description": "Key quote from the content (for questions)"
          },
          "query_answer": {
            "type": "object",
            "description": "Answer to the query (only when query is provided)",
            "properties": {
              "answer": {
                "type": "string"
              },
              "best_segment_index": {
                "type": "integer"
              },
              "relevant_segments": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "VideoSearchResult": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "description": {
            "type": "string"
          },
          "thumbnail": {
            "type": "string",
            "format": "uri"
          },
          "channel": {
            "type": "string"
          },
          "published_date": {
            "type": "string",
            "description": "Publication date"
          },
          "duration": {
            "type": "number",
            "description": "Duration in seconds"
          },
          "views": {
            "type": "integer"
          },
          "likes": {
            "type": "integer"
          },
          "relevance_score": {
            "type": "number",
            "description": "AI relevance score (0-1)"
          },
          "transcript_summary": {
            "type": "string"
          },
          "people": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "context": {
                  "type": "string"
                }
              }
            }
          },
          "places": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "context": {
                  "type": "string"
                }
              }
            }
          },
          "key_subjects": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          },
          "timestamp": {
            "type": "number"
          },
          "relevant_text": {
            "type": "string"
          },
          "query_relevance": {
            "type": "string"
          }
        }
      },
      "Namespace": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique namespace identifier"
          },
          "_id": {
            "type": "string",
            "description": "Same as `id`"
          },
          "user_id": {
            "type": "string",
            "description": "ID of the owning user"
          },
          "name": {
            "type": "string",
            "description": "Namespace display name"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Creation timestamp"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Last update timestamp"
          }
        }
      },
      "ExtractionSchema": {
        "type": "object",
        "description": "Custom extraction schema defining the fields to extract. Max 10 root-level fields, max 3 nesting levels. Each field must have `type` and `description`.",
        "additionalProperties": {
          "type": "object",
          "required": [
            "type",
            "description"
          ],
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "String",
                "Number",
                "Boolean",
                "Integer",
                "Object",
                "Array",
                "Enum"
              ],
              "description": "The data type of the field"
            },
            "description": {
              "type": "string",
              "description": "Description of what this field should contain"
            },
            "properties": {
              "type": "object",
              "description": "Sub-fields for Object type"
            },
            "items": {
              "type": "object",
              "description": "Item schema for Array type"
            },
            "enum": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Allowed values for Enum type"
            }
          }
        },
        "example": {
          "main_topics": {
            "type": "Array",
            "description": "List of main topics discussed",
            "items": {
              "type": "String",
              "description": "A topic"
            }
          },
          "sentiment": {
            "type": "Enum",
            "description": "Overall sentiment of the video",
            "enum": [
              "positive",
              "negative",
              "neutral"
            ]
          },
          "key_takeaway": {
            "type": "String",
            "description": "The single most important takeaway"
          }
        }
      },
      "ExtractionResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ]
          },
          "data": {
            "type": "object",
            "description": "Extracted data matching the provided schema. The shape of this object mirrors the input schema fields."
          },
          "video_info": {
            "allOf": [
              {
                "$ref": "#/components/schemas/VideoInfo"
              }
            ],
            "description": "Video metadata (title, channel, duration, views, etc.). Only present for /extract/video requests."
          },
          "file_info": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FileInfo"
              }
            ],
            "description": "File metadata (name, size, type, duration, etc.). Only present for /extract/file requests."
          },
          "usage": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UsageBlock"
              }
            ],
            "description": "Per-call billing + LLM token usage. Only present when include_usage=true. For /extract/*, the block carries the credit-charge fields (charges, total_credits, credits_remaining_after, waived) AND the LLM token fields (prompt_tokens, completion_tokens, total_tokens) in a single object."
          }
        }
      },
      "FileSearchResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "File ID"
          },
          "name": {
            "type": "string",
            "description": "File name"
          },
          "duration": {
            "type": "number",
            "description": "Duration in seconds"
          },
          "size": {
            "type": "integer",
            "description": "File size in bytes"
          },
          "type": {
            "type": "string",
            "description": "MIME type"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "original_file_date": {
            "type": "string",
            "format": "date-time"
          },
          "file_url": {
            "type": "string",
            "format": "uri",
            "description": "Short-lived signed Google Cloud Storage URL pointing directly at the file bytes. Same model as AWS S3 / CloudFront pre-signed URLs: time-limited and resource-scoped.",
            "example": "https://storage.googleapis.com/bucket/path/to/file?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Signature=..."
          },
          "namespace_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "IDs of the namespaces this file belongs to"
          },
          "namespaces": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NamespaceRef"
            },
            "description": "Resolved namespaces this file belongs to (with names)"
          },
          "relevance_score": {
            "type": "number",
            "description": "AI relevance score (0-1)"
          },
          "timestamps": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "Key moment timestamps in seconds extracted from supporting evidence"
          },
          "relevant_text": {
            "type": "string",
            "description": "Key quote from the file content"
          },
          "query_answer": {
            "type": "string",
            "description": "Direct answer to the query"
          },
          "transcript_summary": {
            "type": "string",
            "description": "Summary of the file's transcript"
          },
          "people": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "context": {
                  "type": "string"
                }
              }
            }
          },
          "places": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "context": {
                  "type": "string"
                }
              }
            }
          },
          "key_subjects": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "TikTokVideo": {
        "type": "object",
        "description": "Public metadata for a single TikTok video returned by the profile scraper.",
        "properties": {
          "id": {
            "type": "string",
            "description": "TikTok video ID"
          },
          "track": {
            "type": "string",
            "nullable": true,
            "description": "Audio track / sound title"
          },
          "artists": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Artist or creator names associated with the audio"
          },
          "duration": {
            "type": "integer",
            "nullable": true,
            "description": "Video length in seconds"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "timestamp": {
            "type": "integer",
            "nullable": true,
            "description": "Unix timestamp (seconds) when the video was uploaded"
          },
          "published_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "UTC datetime string derived from `timestamp` (ISO 8601 with timezone)."
          },
          "views": {
            "type": "integer",
            "nullable": true
          },
          "likes": {
            "type": "integer",
            "nullable": true
          },
          "reposts": {
            "type": "integer",
            "nullable": true
          },
          "comments": {
            "type": "integer",
            "nullable": true
          },
          "thumbnails": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "TikTokProfileFilters": {
        "type": "object",
        "description": "Echo of input filters for the scrape, useful for debugging.",
        "properties": {
          "max_posts": {
            "type": "integer",
            "nullable": true
          },
          "after_datetime": {
            "type": "string",
            "nullable": true,
            "description": "YYYY-MM-DD or ISO datetime with timezone"
          },
          "before_datetime": {
            "type": "string",
            "nullable": true,
            "description": "YYYY-MM-DD or ISO datetime with timezone"
          },
          "min_likes": {
            "type": "integer",
            "nullable": true
          },
          "max_likes": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "TikTokProfileStats": {
        "type": "object",
        "properties": {
          "videos_scanned": {
            "type": "integer",
            "description": "Total entries iterated from TikTok before filtering"
          },
          "videos_matched": {
            "type": "integer",
            "description": "Entries that passed all filters and made it into the result"
          },
          "pages_consumed": {
            "type": "integer",
            "description": "Estimated number of TikTok API pages fetched. Charged 1:1 as transcript retrievals."
          }
        }
      },
      "TikTokProfilePagination": {
        "type": "object",
        "description": "Cursor-based pagination over the videos array. The cursor encodes an absolute offset, so callers can change `limit` between requests without skipping or duplicating items.",
        "properties": {
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "total_items": {
            "type": "integer"
          },
          "has_next": {
            "type": "boolean"
          },
          "has_prev": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "description": "Opaque cursor; pass in `cursor` query param of the next request, or null when has_next is false."
          },
          "prev_cursor": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "TikTokProfileTask": {
        "type": "object",
        "description": "TikTok profile scrape task result body returned by GET /tiktok/profile/{task_id}.",
        "properties": {
          "task_id": {
            "type": "string"
          },
          "task_status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "failed"
            ]
          },
          "profile_url": {
            "type": "string",
            "format": "uri"
          },
          "profile": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Profile metadata (uploader, follower count, etc.). Null while task is still processing."
          },
          "filters": {
            "$ref": "#/components/schemas/TikTokProfileFilters"
          },
          "stats": {
            "$ref": "#/components/schemas/TikTokProfileStats"
          },
          "videos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TikTokVideo"
            },
            "description": "Page of videos according to cursor/limit. Empty while task_status != completed."
          },
          "pagination": {
            "$ref": "#/components/schemas/TikTokProfilePagination"
          },
          "download_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Short-lived (~1 hour) signed Google Cloud Storage URL with the full scrape result as a single JSON file. Same model as AWS S3 / CloudFront pre-signed URLs: time-limited and resource-scoped, so it can be used from browsers, no-code tools, or any HTTP client without forwarding header auth. Set for completed tasks when GCS is configured; otherwise null and clients must paginate. Call GET /tiktok/profile/{task_id} again at any time to mint a fresh URL.",
            "example": "https://storage.googleapis.com/bucket/api/tiktok_profiles/user/task.json?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Signature=..."
          },
          "error_message": {
            "type": "string",
            "nullable": true,
            "description": "Present only when task_status is 'failed'."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the task document and its download URL availability expire (~1 hour after creation)."
          }
        }
      },
      "TikTokSearchResult": {
        "type": "object",
        "description": "Normalized TikTok item from keyword search.",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "item_type": {
            "type": "integer",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "timestamp": {
            "type": "integer",
            "nullable": true
          },
          "published_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "author": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "nullable": true
              },
              "unique_id": {
                "type": "string",
                "nullable": true
              },
              "nickname": {
                "type": "string",
                "nullable": true
              },
              "sec_uid": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "stats": {
            "type": "object",
            "properties": {
              "views": {
                "type": "integer",
                "nullable": true
              },
              "likes": {
                "type": "integer",
                "nullable": true
              },
              "comments": {
                "type": "integer",
                "nullable": true
              },
              "shares": {
                "type": "integer",
                "nullable": true
              },
              "collects": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "music": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "nullable": true
              },
              "title": {
                "type": "string",
                "nullable": true
              },
              "author_name": {
                "type": "string",
                "nullable": true
              },
              "duration": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "duration": {
            "type": "integer",
            "nullable": true
          },
          "hashtags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          }
        }
      },
      "TikTokSearchTask": {
        "type": "object",
        "description": "TikTok keyword search task result body returned by GET /tiktok/search/{task_id}. `results` are sorted by `published_at` descending (newest first); items without a timestamp go last.",
        "properties": {
          "task_id": {
            "type": "string"
          },
          "task_status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "failed"
            ]
          },
          "query": {
            "type": "string"
          },
          "parallel_search_slices": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "description": "Slice count the task was created with. An N-slice request ran as N concurrent paginated TikTok query chains and was billed at up to ~Nx the residential pages of a single search."
          },
          "filters": {
            "type": "object",
            "additionalProperties": true,
            "description": "Echo of the client-side filters supplied at task creation (`after_datetime`, `before_datetime`, `min_likes`, `max_likes`, `min_views`, `max_views`). Unset fields are returned as null.",
            "properties": {
              "after_datetime": {
                "type": "string",
                "nullable": true
              },
              "before_datetime": {
                "type": "string",
                "nullable": true
              },
              "min_likes": {
                "type": "integer",
                "nullable": true
              },
              "max_likes": {
                "type": "integer",
                "nullable": true
              },
              "min_views": {
                "type": "integer",
                "nullable": true
              },
              "max_views": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "stats": {
            "type": "object",
            "properties": {
              "pages_fetched": {
                "type": "integer"
              },
              "results_count": {
                "type": "integer"
              },
              "next_search_cursor": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TikTokSearchResult"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/TikTokProfilePagination"
          },
          "download_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Short-lived (~1 hour) signed Google Cloud Storage URL with the full search result as a single JSON file (same shape as paginated `results` but unsliced). Same model as AWS S3 / CloudFront pre-signed URLs: time-limited and resource-scoped, so it can be used from browsers, no-code tools, or any HTTP client without forwarding header auth. Set for completed tasks when GCS is configured; otherwise null and clients must paginate. Call GET /tiktok/search/{task_id} again at any time to mint a fresh URL.",
            "example": "https://storage.googleapis.com/bucket/api/tiktok_searches/user/task.json?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Signature=..."
          },
          "error_message": {
            "type": "string",
            "nullable": true,
            "description": "Present only when task_status is 'failed'."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the task document and its download URL availability expire (~1 hour after creation)."
          }
        }
      }
    },
    "responses": {
      "ContentRestricted": {
        "description": "Content restricted - requires login or age verification",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "content_restricted"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "GeoRestricted": {
        "description": "Content not available in your region",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "geo_restricted"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "BadRequest": {
        "description": "Bad request - invalid parameters",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "AccessDenied": {
        "description": "Access denied - insufficient permissions",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "access_denied"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "FileNotFound": {
        "description": "File not found",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "file_not_found"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "VideoNotFound": {
        "description": "Video not found or transcript unavailable",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "video_not_found",
                    "transcript_not_available"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "TranscriptNotFound": {
        "description": "Transcript not available for this video",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "transcript_not_available"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Usage limit exceeded - upgrade required",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "limit_exceeded"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "RateLimitExceeded": {
        "description": "Rate limit exceeded",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "limit_exceeded"
                  ]
                },
                "message": {
                  "type": "string"
                },
                "usage": {
                  "type": "integer"
                },
                "limit": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "StorageQuotaExceeded": {
        "description": "Storage quota exceeded",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "storage_quota_exceeded"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "StorageNotConfigured": {
        "description": "File storage not configured on server",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "storage_not_configured"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "SystemOverload": {
        "description": "System is temporarily overloaded",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "system_overload"
                  ]
                },
                "message": {
                  "type": "string"
                },
                "retry_after_seconds": {
                  "type": "integer",
                  "description": "Recommended number of seconds to wait before retrying"
                },
                "charge_user": {
                  "type": "boolean",
                  "description": "Always false for overload errors \u2014 the request was not billed"
                }
              }
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "error"
                  ]
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "internal_server_error"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Transcripts",
      "description": "Extract transcripts from online videos"
    },
    {
      "name": "TikTok",
      "description": "Scrape TikTok profile metadata and per-video stats"
    },
    {
      "name": "Files",
      "description": "Manage uploaded audio/video files"
    },
    {
      "name": "Analysis",
      "description": "AI-powered content analysis"
    },
    {
      "name": "Extraction",
      "description": "Extract structured data from video and file transcripts using custom schemas"
    },
    {
      "name": "Namespaces",
      "description": "Organize uploaded files into namespaces (folders)"
    },
    {
      "name": "Search",
      "description": "Search videos and files using AI"
    },
    {
      "name": "System",
      "description": "System health and information"
    },
    {
      "name": "Tweet Analysis",
      "description": "Extract structured claims and metadata from X/Twitter tweets"
    }
  ]
}
