{
  "openapi": "3.1.0",
  "info": {
    "title": "HookFindr API",
    "version": "1.0.0",
    "summary": "Multi-platform short-form video transcription, metadata, SRT export, and hook analysis.",
    "description": "Drop any public TikTok / YouTube / Instagram Reels / Twitter-X / Twitch / Facebook URL (~1000 platforms via yt-dlp) and get back transcript with word-level timestamps, SRT subtitles, opening-hook classification, or metadata. Three tiers: anonymous free (5 transcripts per IP per 30 days), magic-link email (5/day), and x402 pay-per-call (USDC on Base, no signup). x402 endpoints listed on the Coinbase Bazaar.",
    "contact": {
      "name": "HookFindr",
      "url": "https://hookfindr.com",
      "email": "hello@hookfindr.com"
    },
    "license": {
      "name": "MIT (worker source)",
      "url": "https://github.com/refinedsolutionsit-hub/hookfindr/blob/main/LICENSE"
    }
  },
  "servers": [
    { "url": "https://api.hookfindr.com", "description": "Production API" },
    { "url": "https://x402.hookfindr.com", "description": "x402-convention alias (same worker)" }
  ],
  "tags": [
    { "name": "free", "description": "Anonymous-free tier (IP rate-limited)" },
    { "name": "x402", "description": "x402-paid tier — USDC on Base, no signup" },
    { "name": "system", "description": "System endpoints" }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "tags": ["system"],
        "summary": "Health check",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "Worker is up",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } }
          }
        }
      }
    },
    "/api/info": {
      "post": {
        "tags": ["free"],
        "summary": "Get video metadata (free tier)",
        "description": "Fast yt-dlp metadata extract. Counts against the anonymous IP rate limit (5 per 30d).",
        "operationId": "getInfoFree",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UrlRequest" } } } },
        "responses": {
          "200": { "description": "Metadata", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VideoInfo" } } } },
          "400": { "$ref": "#/components/responses/InvalidUrl" },
          "404": { "$ref": "#/components/responses/VideoUnavailable" },
          "422": { "$ref": "#/components/responses/ExtractFailed" }
        }
      }
    },
    "/api/transcribe": {
      "post": {
        "tags": ["free"],
        "summary": "Transcribe video to text with word timestamps (free tier)",
        "description": "Native captions first (~2s) → Whisper-large-v3-turbo fallback (~30s). LRCLIB lyrics substitution when audio is a known song. Counts against the anonymous IP rate limit (5 per 30d).",
        "operationId": "transcribeFree",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UrlRequest" } } } },
        "responses": {
          "200": { "description": "Transcript", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Transcript" } } } },
          "400": { "$ref": "#/components/responses/InvalidUrl" },
          "404": { "$ref": "#/components/responses/VideoUnavailable" },
          "413": { "$ref": "#/components/responses/VideoTooLong" },
          "422": { "$ref": "#/components/responses/ExtractFailed" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/x402/info": {
      "post": {
        "tags": ["x402"],
        "summary": "Get video metadata (paid, $0.005 USDC on Base)",
        "operationId": "getInfoX402",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UrlRequest" } } } },
        "responses": {
          "200": { "description": "Metadata (after payment)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VideoInfo" } } } },
          "402": { "$ref": "#/components/responses/PaymentRequired" }
        },
        "security": [{ "x402": [] }]
      }
    },
    "/api/x402/transcribe": {
      "post": {
        "tags": ["x402"],
        "summary": "Transcribe video to text (paid, $0.05 USDC on Base)",
        "operationId": "transcribeX402",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UrlRequest" } } } },
        "responses": {
          "200": { "description": "Transcript (after payment)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Transcript" } } } },
          "402": { "$ref": "#/components/responses/PaymentRequired" }
        },
        "security": [{ "x402": [] }]
      }
    },
    "/api/x402/caption.srt": {
      "post": {
        "tags": ["x402"],
        "summary": "Export SRT subtitle file (paid, $0.05 USDC on Base)",
        "operationId": "captionSrtX402",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UrlRequest" } } } },
        "responses": {
          "200": { "description": "SRT subtitle text", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CaptionSrt" } } } },
          "402": { "$ref": "#/components/responses/PaymentRequired" }
        },
        "security": [{ "x402": [] }]
      }
    },
    "/api/x402/hook": {
      "post": {
        "tags": ["x402"],
        "summary": "Analyze video opening hook (paid, $0.02 USDC on Base)",
        "operationId": "hookX402",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UrlRequest" } } } },
        "responses": {
          "200": { "description": "Hook classification + strength + explanation", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HookAnalysis" } } } },
          "402": { "$ref": "#/components/responses/PaymentRequired" }
        },
        "security": [{ "x402": [] }]
      }
    }
  },
  "components": {
    "schemas": {
      "UrlRequest": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Public URL of a short-form video. Supports TikTok, YouTube, YouTube Shorts, Instagram Reels, Twitter/X, Twitch clips, Facebook video, and ~1000 other platforms via yt-dlp.",
            "examples": ["https://www.tiktok.com/@benxwilliams222/video/7639190773732232478"]
          }
        }
      },
      "VideoInfo": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "uploader": { "type": "string" },
          "duration": { "type": "number", "description": "Seconds." },
          "extractor": { "type": "string", "description": "Platform name (TikTok, YouTube, Instagram, etc.)" },
          "id": { "type": "string", "description": "Platform-native video id." },
          "webpage_url": { "type": "string", "format": "uri" },
          "track": { "type": ["string", "null"], "description": "Detected music track if applicable." },
          "artist": { "type": ["string", "null"] },
          "album": { "type": ["string", "null"] }
        }
      },
      "Word": {
        "type": "object",
        "properties": {
          "word": { "type": "string" },
          "start": { "type": "number", "description": "Seconds from start of clip." },
          "end": { "type": "number" }
        }
      },
      "Transcript": {
        "type": "object",
        "properties": {
          "text": { "type": "string", "description": "Full transcript." },
          "words": {
            "type": "array",
            "description": "Word-level timestamps. Empty when transcript came from lyrics substitution or native captions without timing.",
            "items": { "$ref": "#/components/schemas/Word" }
          },
          "word_count": { "type": "number" },
          "duration_s": { "type": ["number", "null"] },
          "transcript_source": {
            "type": "string",
            "enum": ["native", "whisper_v3_turbo", "lyrics_substitution"],
            "description": "How the transcript was produced."
          },
          "music": {
            "type": ["object", "null"],
            "description": "Present when the audio contains an identified song. Includes plain_lyrics, synced_lyrics, and streaming links when LRCLIB matches."
          },
          "summary": { "type": ["string", "null"], "description": "1-2 sentence TL;DR for long transcripts." },
          "tier": { "type": "string", "enum": ["anon", "email", "operator", "x402"] },
          "remaining": { "type": "number", "description": "Free-tier calls remaining in the current window (anon/email only)." }
        }
      },
      "CaptionSrt": {
        "type": "object",
        "properties": {
          "srt": { "type": "string", "description": "Captions in SRT format, ready to upload to YouTube, IG Reels, TikTok, or any subtitle player." },
          "word_count": { "type": "number" },
          "duration_s": { "type": ["number", "null"] },
          "source": { "type": "string", "description": "Underlying transcript source." },
          "has_word_timestamps": { "type": "boolean" }
        }
      },
      "HookAnalysis": {
        "type": "object",
        "properties": {
          "hook_text": { "type": "string", "description": "Verbatim words from the first 8 seconds that constitute the hook." },
          "hook_category": {
            "type": "string",
            "enum": ["question", "presupposition", "shock_statement", "list_promise", "identity_attack", "mystery", "before_after", "contrarian", "narrative", "demonstration", "unknown"]
          },
          "strength": { "type": ["number", "null"], "minimum": 1, "maximum": 10, "description": "Hook strength (LLM judgment)." },
          "why_it_works": { "type": "string", "description": "One-sentence explanation of the psychological mechanic." },
          "analyzed_seconds": { "type": "number" },
          "source_transcript": { "type": "string" }
        }
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "examples": ["ok"] },
          "version": { "type": "string" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "Stable machine-readable code." },
          "message": { "type": "string", "description": "Human-readable detail." }
        },
        "required": ["error"]
      },
      "PaymentRequiredEnvelope": {
        "type": "object",
        "description": "x402 v2 envelope. Sent in the `payment-required` response header (base64-encoded JSON), with `{}` as the body.",
        "properties": {
          "x402Version": { "type": "number", "const": 2 },
          "error": { "type": "string" },
          "resource": {
            "type": "object",
            "properties": {
              "url": { "type": "string", "format": "uri" },
              "description": { "type": "string" },
              "mimeType": { "type": "string" }
            }
          },
          "accepts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "scheme": { "type": "string", "examples": ["exact"] },
                "network": { "type": "string", "examples": ["eip155:8453"] },
                "amount": { "type": "string", "description": "Atomic units of the asset (USDC has 6 decimals)." },
                "asset": { "type": "string", "examples": ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"] },
                "payTo": { "type": "string", "examples": ["0xaE0fEa8bb97DB83FFc96BAada9A4d8Da6efd21ba"] },
                "maxTimeoutSeconds": { "type": "number" },
                "extra": { "type": "object", "properties": { "name": { "type": "string" }, "version": { "type": "string" } } }
              }
            }
          },
          "extensions": {
            "type": "object",
            "description": "Bazaar discovery extension. Crawler reads this to index the endpoint."
          }
        }
      }
    },
    "responses": {
      "InvalidUrl": {
        "description": "Missing or malformed URL in request body.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "missing url" } } }
      },
      "VideoUnavailable": {
        "description": "Video is private, deleted, or geo-blocked.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "video_unavailable", "message": "Video is private or unavailable." } } }
      },
      "VideoTooLong": {
        "description": "Over 10 minutes (current limit).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "video_too_long", "message": "This video is longer than we can currently transcribe in one pass. Try a clip under 10 minutes." } } }
      },
      "ExtractFailed": {
        "description": "yt-dlp couldn't extract the audio (geoblock, paywall, format unsupported).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "extract_failed", "message": "Couldn't extract audio: ..." } } }
      },
      "RateLimited": {
        "description": "Quota exhausted for this tier. Includes `tier`, `remaining`, and `resetIn` in the body.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "rate_limit", "tier": "anon", "message": "You've used your 3 free transcripts for the month.", "remaining": 0, "resetIn": 2592000 } } }
      },
      "PaymentRequired": {
        "description": "x402 v2 payment required. The payment requirements live in the `payment-required:` response header (base64-encoded JSON). Body is `{}`.",
        "headers": {
          "payment-required": {
            "description": "Base64-encoded JSON matching PaymentRequiredEnvelope.",
            "schema": { "type": "string", "format": "byte" }
          }
        },
        "content": { "application/json": { "schema": { "type": "object" }, "example": {} } }
      }
    },
    "securitySchemes": {
      "x402": {
        "type": "http",
        "scheme": "x402",
        "description": "x402 v2 protocol. Client receives 402 with payment requirements in the `payment-required` header, signs an EIP-3009 TransferWithAuthorization for USDC on Base mainnet (chainId 8453), and retries with the signed payload in the `PAYMENT-SIGNATURE` header. Facilitator: Coinbase CDP (api.cdp.coinbase.com/platform/v2/x402)."
      }
    }
  },
  "externalDocs": {
    "description": "Full integration guide + code examples",
    "url": "https://github.com/refinedsolutionsit-hub/hookfindr/blob/main/docs/agents.md"
  }
}
