{
  "openapi": "3.0.0",
  "info": {
    "title": "LLMLayer API",
    "version": "2.0.0",
    "description": "The Web API for AI Agents - Search, scrape, extract content, crawl websites, and generate AI-powered answers with multiple LLMs"
  },
  "servers": [
    {
      "url": "https://api.llmlayer.dev"
    }
  ],
  "paths": {
    "/api/v2/answer": {
      "post": {
        "summary": "Answer API - Web-enhanced AI responses",
        "operationId": "answer",
        "description": "Search the web and generate AI-powered answers using your chosen LLM model. Supports multiple search queries, custom prompts, and structured JSON output.",
        "tags": [
          "Answer API"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response with AI-generated answer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitError"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/ProviderError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/api/v2/answer_stream": {
      "post": {
        "summary": "Answer API - Stream responses in real-time",
        "operationId": "answerStream",
        "description": "Stream search results and AI responses in real-time using Server-Sent Events (SSE). Does not support JSON structured output.",
        "tags": [
          "Answer API"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Server-Sent Events stream",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE stream with event types: sources, images, answer, usage, done, error"
                },
                "examples": {
                  "sources_event": {
                    "value": "data: {\"type\":\"sources\",\"data\":[{\"title\":\"Y Combinator\",\"link\":\"https://www.ycombinator.com\",\"snippet\":\"...\"}]}\n\n"
                  },
                  "images_event": {
                    "value": "data: {\"type\":\"images\",\"data\":[{\"title\":\"Image\",\"imageUrl\":\"https://...\"}]}\n\n"
                  },
                  "answer_event": {
                    "value": "data: {\"type\":\"answer\",\"content\":\"This is part of the answer...\"}\n\n"
                  },
                  "usage_event": {
                    "value": "data: {\"type\":\"usage\",\"input_tokens\":1500,\"output_tokens\":800,\"model_cost\":0.0025,\"llmlayer_cost\":0.004}\n\n"
                  },
                  "done_event": {
                    "value": "data: {\"type\":\"done\",\"response_time\":\"3.45\"}\n\n"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/api/v2/web_search": {
      "post": {
        "summary": "Web Search API - Raw search results",
        "operationId": "webSearch",
        "description": "Search the web across multiple content types (general, news, shopping, videos, images, scholar) without AI processing. Returns raw search results.",
        "tags": [
          "Web Search"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebSearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Raw search results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebSearchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/api/v2/scrape": {
      "post": {
        "summary": "Scraper API - Multi-format content extraction",
        "operationId": "scrape",
        "description": "Extract content from a non-PDF URL as markdown, HTML, or a screenshot (base64). Direct PDF URLs are rejected; use /api/v2/get_pdf_content.",
        "tags": [
          "Content Extraction"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScrapeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scraped content in requested formats",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScraperResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/api/v2/get_pdf_content": {
      "post": {
        "summary": "PDF Content API - Extract text from PDFs",
        "operationId": "getPdfContent",
        "description": "Extract text content from PDF documents. 15-second timeout with proper 504 handling.",
        "tags": [
          "Content Extraction"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PDFRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extracted PDF content with page count",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PDFResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "504": {
            "$ref": "#/components/responses/TimeoutError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/api/v2/youtube_transcript": {
      "post": {
        "summary": "YouTube Transcript API - Extract video transcripts",
        "operationId": "getYoutubeTranscript",
        "description": "Extract transcripts from YouTube videos with optional language selection. Returns transcript plus video metadata (title, author, views, likes, date).",
        "tags": [
          "Content Extraction"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/YTRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extracted transcript with video metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/YTResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/api/v2/map": {
      "post": {
        "summary": "Map API - Discover URLs on a website",
        "operationId": "mapWebsite",
        "description": "Firecrawl-style URL discovery. Maps all URLs on a website with lightweight titles (no content extraction). Useful for site exploration before crawling.",
        "tags": [
          "Web Crawling"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MapRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "List of discovered URLs with titles",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MapResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/api/v2/crawl_stream": {
      "post": {
        "summary": "Crawl API - Stream markdown pages from a website",
        "operationId": "crawlWebsite",
        "description": "Crawl multiple pages from a website with depth control. Streams Server-Sent Events as pages are scraped. The public endpoint currently returns markdown content per page. Billed per successfully scraped page ($0.001/page).",
        "tags": [
          "Web Crawling"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CrawlRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Server-Sent Events stream of crawled pages",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE stream with event types: page, usage, done, error"
                },
                "examples": {
                  "page_event": {
                    "value": "data: {\"type\":\"page\",\"page\":{\"requested_url\":\"https://www.ycombinator.com\",\"final_url\":\"https://www.ycombinator.com\",\"title\":\"Y Combinator\",\"hash_sha256\":\"abc123...\",\"markdown\":\"# Content...\",\"success\":true}}\n\n"
                  },
                  "usage_event": {
                    "value": "data: {\"type\":\"usage\",\"billed_count\":25,\"unit_cost\":0.001,\"cost\":0.025}\n\n"
                  },
                  "done_event": {
                    "value": "data: {\"type\":\"done\",\"response_time\":\"45.67\"}\n\n"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/api/v2/extract": {
      "post": {
        "summary": "Extract API - Multi-mode page extraction",
        "operationId": "extract",
        "description": "Extract from a single web page in one call. Combine any modes: structured JSON ('json', $0.005), a markdown summary ('summary', $0.005), an answer to a question ('qa', $0.005), all page links ('links', $0.001), and a brand profile with logos/colors/socials ('brand', $0.002). Pricing is summed per selected mode; advanced proxy adds $0.004 once when a scrape runs. If the request fails before any AI cost is incurred (page fetch failure, empty content, brand fetch failure), you are fully refunded.",
        "tags": [
          "Content Extraction"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extraction results. All result fields (structured_data, summary, answer, links, brand) are always present; modes you did not request are null.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "The page returned no extractable content (fully refunded), or the JSON output was truncated before completion.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimitError"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "SearchRequest": {
        "type": "object",
        "required": [
          "query",
          "model"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "The search query or question to answer",
            "example": "What are the latest developments in quantum computing?"
          },
          "model": {
            "type": "string",
            "description": "LLM model to use (e.g., llmlayer-web, llmlayer-fast, openai/gpt-4o-mini, openai/gpt-5.1)",
            "example": "llmlayer-web"
          },
          "location": {
            "type": "string",
            "default": "us",
            "description": "Country code for localized search results (us, uk, ca, etc.)",
            "example": "us"
          },
          "provider_key": {
            "type": "string",
            "nullable": true,
            "description": "Deprecated. Accepted for backward compatibility but currently ignored by the API."
          },
          "system_prompt": {
            "type": "string",
            "nullable": true,
            "description": "Custom system prompt to override default behavior. Use this to customize how the LLM processes the search results."
          },
          "response_language": {
            "type": "string",
            "default": "auto",
            "description": "Language for the response. 'auto' detects from query, or specify language code (en, es, fr, etc.)",
            "example": "auto"
          },
          "answer_type": {
            "type": "string",
            "enum": [
              "markdown",
              "html",
              "json"
            ],
            "default": "markdown",
            "description": "Format of the response. Use 'json' with json_schema for structured output."
          },
          "search_type": {
            "type": "string",
            "enum": [
              "general",
              "news"
            ],
            "default": "general",
            "description": "Type of web search to perform. 'news' provides recent news articles."
          },
          "json_schema": {
            "type": "string",
            "nullable": true,
            "description": "JSON schema as string for structured responses. Required when answer_type='json'. The LLM will format its response according to this schema.",
            "example": "{\"type\":\"object\",\"properties\":{\"summary\":{\"type\":\"string\"},\"key_points\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}}"
          },
          "citations": {
            "type": "boolean",
            "default": false,
            "description": "Include inline citations [1], [2] in the response text"
          },
          "return_sources": {
            "type": "boolean",
            "default": false,
            "description": "Return the source documents used for answer generation"
          },
          "return_images": {
            "type": "boolean",
            "default": false,
            "description": "Return relevant images from image search. Adds $0.001 to cost."
          },
          "date_filter": {
            "type": "string",
            "enum": [
              "anytime",
              "hour",
              "day",
              "week",
              "month",
              "year"
            ],
            "default": "anytime",
            "description": "Filter search results by recency. Useful for time-sensitive queries."
          },
          "max_tokens": {
            "type": "integer",
            "default": 1500,
            "minimum": 1,
            "description": "Maximum tokens in the LLM response. Affects cost."
          },
          "temperature": {
            "type": "number",
            "default": 0.7,
            "minimum": 0,
            "maximum": 2,
            "description": "Controls response randomness. 0=deterministic, 2=very creative. Not supported by all models."
          },
          "domain_filter": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "Include or exclude specific domains. Use '-' prefix to exclude (e.g., ['-reddit.com', 'wikipedia.org'])",
            "example": [
              "wikipedia.org",
              "-reddit.com"
            ]
          },
          "max_queries": {
            "type": "integer",
            "default": 1,
            "minimum": 1,
            "maximum": 4,
            "description": "Number of search queries to generate from the user query. More queries = broader search and higher LLMLayer fee. Token-priced models use $0.004 per query; fixed-price LLMLayer models use their model-specific per-query price.",
            "example": 3
          },
          "search_context_size": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ],
            "default": "medium",
            "description": "Amount of search context to extract and pass to LLM. 'high' provides more context but uses more tokens."
          }
        }
      },
      "SearchResponse": {
        "type": "object",
        "required": [
          "answer",
          "response_time",
          "input_tokens",
          "output_tokens",
          "llmlayer_cost"
        ],
        "properties": {
          "answer": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "additionalProperties": true
              }
            ],
            "description": "The AI-generated answer. String for markdown/html, object for JSON responses."
          },
          "sources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Source"
            },
            "description": "Source documents used (only when return_sources=true)"
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Image"
            },
            "description": "Relevant images (only when return_images=true)"
          },
          "response_time": {
            "type": "string",
            "description": "Total processing time in seconds",
            "example": "2.34"
          },
          "input_tokens": {
            "type": "integer",
            "description": "Total input tokens processed (includes query generation + main LLM call + internal system prompts)"
          },
          "output_tokens": {
            "type": "integer",
            "description": "Total output tokens generated (includes query generation + main LLM response)"
          },
          "model_cost": {
            "type": "number",
            "nullable": true,
            "description": "Cost in USD for LLM usage. Null for fixed-price LLMLayer models."
          },
          "llmlayer_cost": {
            "type": "number",
            "description": "Cost in USD for LLMLayer search infrastructure. Base cost varies by max_queries and return_images."
          }
        }
      },
      "WebSearchRequest": {
        "type": "object",
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "The search query",
            "example": "latest AI news"
          },
          "search_type": {
            "type": "string",
            "enum": [
              "general",
              "news",
              "shopping",
              "videos",
              "images",
              "scholar"
            ],
            "default": "general",
            "description": "Type of search. All types cost $0.002 per search."
          },
          "location": {
            "type": "string",
            "default": "us",
            "description": "Country code for localized results (us, uk, ca, etc.)",
            "example": "us"
          },
          "recency": {
            "type": "string",
            "enum": [
              "hour",
              "day",
              "week",
              "month",
              "year"
            ],
            "nullable": true,
            "description": "Filter results by time period"
          },
          "domain_filter": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "Include/exclude domains. Prefix with '-' to exclude (e.g., ['-reddit.com'])",
            "example": [
              "wikipedia.org",
              "-pinterest.com"
            ]
          }
        }
      },
      "WebSearchResponse": {
        "type": "object",
        "required": [
          "results"
        ],
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Search results. Structure varies by search_type."
          },
          "cost": {
            "type": "number",
            "description": "Cost in USD. $0.002 for all search types."
          }
        }
      },
      "ScrapeRequest": {
        "type": "object",
        "required": [
          "url",
          "formats"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL to scrape",
            "example": "https://www.ycombinator.com/blog"
          },
          "formats": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "markdown",
                "html",
                "screenshot"
              ]
            },
            "description": "Output formats to generate: markdown, html, or screenshot. Direct PDF URLs are rejected; use /api/v2/get_pdf_content. If older clients send 'pdf', it is ignored.",
            "example": [
              "markdown",
              "screenshot"
            ]
          },
          "include_images": {
            "type": "boolean",
            "default": true,
            "description": "Include images in markdown output"
          },
          "include_links": {
            "type": "boolean",
            "default": true,
            "description": "Include hyperlinks in markdown output"
          },
          "advanced_proxy": {
            "type": "boolean",
            "default": false,
            "nullable": true,
            "description": "Enable advanced proxy for heavily protected sites."
          },
          "main_content_only": {
            "type": "boolean",
            "default": false,
            "nullable": true,
            "description": "Extract only the main content, excluding navigation and boilerplate."
          }
        }
      },
      "ScraperResponse": {
        "type": "object",
        "required": [
          "url",
          "statusCode"
        ],
        "properties": {
          "markdown": {
            "type": "string",
            "description": "Markdown content when available/requested.",
            "nullable": true
          },
          "html": {
            "type": "string",
            "nullable": true,
            "description": "Content in HTML format (when 'html' in formats)"
          },
          "pdf": {
            "type": "string",
            "nullable": true,
            "description": "Legacy field. Present for backward compatibility; usually null/empty."
          },
          "screenshot": {
            "type": "string",
            "nullable": true,
            "description": "Base64-encoded screenshot image (when 'screenshot' in formats)"
          },
          "url": {
            "type": "string",
            "description": "Final URL after any redirects"
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Page title extracted from metadata"
          },
          "statusCode": {
            "type": "integer",
            "description": "HTTP status code (200 for success)"
          },
          "cost": {
            "type": "number",
            "description": "Cost in USD ($0.001 per format requested)"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Additional metadata extracted from the page"
          }
        }
      },
      "PDFRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Direct URL to PDF document",
            "example": "https://www.ycombinator.com/blog/content/files/2024/06/RemedyFest-Final-Report.pdf"
          }
        }
      },
      "PDFResponse": {
        "type": "object",
        "required": [
          "text",
          "pages",
          "url",
          "statusCode"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "Extracted text content from all pages"
          },
          "pages": {
            "type": "integer",
            "description": "Number of pages in the PDF"
          },
          "url": {
            "type": "string",
            "description": "The PDF URL"
          },
          "statusCode": {
            "type": "integer",
            "description": "HTTP status code (200 for success)"
          },
          "cost": {
            "type": "number",
            "description": "Cost in USD ($0.002 per PDF extraction)"
          }
        }
      },
      "YTRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "YouTube video URL (supports youtube.com and youtu.be)",
            "example": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
          },
          "language": {
            "type": "string",
            "nullable": true,
            "description": "Language code for transcript (e.g., 'en', 'es', 'fr'). If not specified, uses video's default language.",
            "example": "en"
          }
        }
      },
      "YTResponse": {
        "type": "object",
        "required": [
          "transcript",
          "url"
        ],
        "properties": {
          "transcript": {
            "type": "string",
            "description": "Full video transcript text"
          },
          "url": {
            "type": "string",
            "description": "YouTube video URL"
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Video title"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Video description"
          },
          "author": {
            "type": "string",
            "nullable": true,
            "description": "Channel name/author"
          },
          "views": {
            "type": "integer",
            "nullable": true,
            "description": "View count"
          },
          "likes": {
            "type": "integer",
            "nullable": true,
            "description": "Like count"
          },
          "date": {
            "type": "string",
            "nullable": true,
            "description": "Upload date"
          },
          "cost": {
            "type": "number",
            "nullable": true,
            "description": "Cost in USD (value from COST_YOUTUBE_TRANSCRIPT constant)"
          },
          "language": {
            "type": "string",
            "nullable": true,
            "description": "Language of the returned transcript"
          }
        }
      },
      "MapRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Seed URL to start mapping from",
            "example": "https://www.ycombinator.com"
          },
          "ignoreSitemap": {
            "type": "boolean",
            "default": false,
            "description": "If true, ignores sitemap.xml and discovers URLs by crawling"
          },
          "includeSubdomains": {
            "type": "boolean",
            "default": false,
            "description": "If true, includes URLs from subdomains"
          },
          "search": {
            "type": "string",
            "nullable": true,
            "description": "Filter discovered URLs by search string",
            "example": "blog"
          },
          "limit": {
            "type": "integer",
            "default": 5000,
            "maximum": 30000,
            "description": "Maximum number of URLs to discover (hard cap: 30000)"
          },
          "timeout": {
            "type": "integer",
            "nullable": true,
            "default": 45000,
            "description": "Timeout in milliseconds"
          }
        }
      },
      "MapResponse": {
        "type": "object",
        "required": [
          "links",
          "statusCode"
        ],
        "properties": {
          "links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MapLink"
            },
            "description": "List of discovered URLs with titles"
          },
          "statusCode": {
            "type": "integer",
            "description": "HTTP status code (200 for success)"
          },
          "cost": {
            "type": "number",
            "nullable": true,
            "description": "Cost in USD ($0.002 flat fee per map request)"
          }
        }
      },
      "MapLink": {
        "type": "object",
        "required": [
          "url",
          "title"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Discovered URL"
          },
          "title": {
            "type": "string",
            "description": "Page title"
          }
        }
      },
      "CrawlRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Seed URL to start crawling from",
            "example": "https://www.ycombinator.com"
          },
          "max_pages": {
            "type": "integer",
            "default": 25,
            "minimum": 1,
            "maximum": 100,
            "description": "Maximum number of pages to crawl (hard limit: 100)"
          },
          "max_depth": {
            "type": "integer",
            "default": 2,
            "minimum": 1,
            "description": "Maximum depth to crawl from seed URL"
          },
          "timeout": {
            "type": "number",
            "nullable": true,
            "default": 60,
            "description": "Total timeout in seconds for the entire crawl operation"
          },
          "include_subdomains": {
            "type": "boolean",
            "default": false,
            "description": "If true, includes pages from subdomains"
          },
          "include_links": {
            "type": "boolean",
            "default": true,
            "description": "Include hyperlinks in extracted content"
          },
          "include_images": {
            "type": "boolean",
            "default": true,
            "description": "Include images in extracted content"
          },
          "advanced_proxy": {
            "type": "boolean",
            "default": false,
            "nullable": true,
            "description": "Enable advanced proxy for protected sites."
          },
          "main_content_only": {
            "type": "boolean",
            "default": false,
            "nullable": true,
            "description": "Extract only main page content."
          },
          "formats": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "markdown"
              ]
            },
            "default": [
              "markdown"
            ],
            "description": "Accepted for compatibility; only markdown is currently honored by /api/v2/crawl_stream.",
            "example": [
              "markdown"
            ]
          }
        }
      },
      "CrawlResponse": {
        "type": "object",
        "required": [
          "seeds",
          "fetched",
          "visited",
          "max_pages",
          "max_depth",
          "results",
          "graph",
          "errors",
          "billing",
          "statusCode"
        ],
        "properties": {
          "seeds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of seed URLs used to start the crawl"
          },
          "fetched": {
            "type": "integer",
            "description": "Number of pages successfully fetched"
          },
          "visited": {
            "type": "integer",
            "description": "Total number of unique URLs visited"
          },
          "max_pages": {
            "type": "integer",
            "description": "Maximum pages limit for this crawl"
          },
          "max_depth": {
            "type": "integer",
            "description": "Maximum depth limit for this crawl"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CrawlResultItem"
            },
            "description": "Array of crawled page results"
          },
          "graph": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Graph of URLs and their outbound links (adjacency list)"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "List of errors encountered during crawl"
          },
          "billing": {
            "type": "object",
            "additionalProperties": true,
            "description": "Billing information and breakdown"
          },
          "partial": {
            "type": "boolean",
            "nullable": true,
            "description": "True if crawl was incomplete (hit limits or timeout)"
          },
          "statusCode": {
            "type": "integer",
            "description": "HTTP status code (200 for success)"
          },
          "cost": {
            "type": "number",
            "nullable": true,
            "description": "Total cost in USD ($0.001 per successfully scraped page)"
          }
        },
        "description": "Legacy non-streaming crawl response shape. The public /api/v2/crawl_stream endpoint returns Server-Sent Events with markdown page content."
      },
      "CrawlResultItem": {
        "type": "object",
        "required": [
          "requested_url",
          "final_url",
          "title",
          "hash_sha256"
        ],
        "properties": {
          "requested_url": {
            "type": "string",
            "description": "Originally requested URL"
          },
          "final_url": {
            "type": "string",
            "description": "Final URL after redirects"
          },
          "title": {
            "type": "string",
            "description": "Page title"
          },
          "hash_sha256": {
            "type": "string",
            "description": "SHA-256 hash of page content"
          },
          "markdown": {
            "type": "string",
            "nullable": true,
            "description": "Markdown content for the crawled page."
          },
          "html": {
            "type": "string",
            "nullable": true,
            "description": "Legacy/pass-through field; crawl currently returns markdown only."
          },
          "screenshot": {
            "type": "string",
            "nullable": true,
            "description": "Legacy/pass-through field; crawl currently returns markdown only."
          },
          "pdf": {
            "type": "string",
            "nullable": true,
            "description": "Legacy/pass-through field; crawl currently returns markdown only."
          },
          "success": {
            "type": "boolean",
            "nullable": true,
            "description": "Whether the page was successfully scraped"
          },
          "error": {
            "type": "string",
            "nullable": true,
            "description": "Error message if scraping failed"
          }
        }
      },
      "Source": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Title of the source document"
          },
          "link": {
            "type": "string",
            "format": "uri",
            "description": "URL of the source"
          },
          "snippet": {
            "type": "string",
            "description": "Relevant excerpt from the source"
          }
        },
        "additionalProperties": true
      },
      "Image": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Image title or alt text"
          },
          "imageUrl": {
            "type": "string",
            "format": "uri",
            "description": "Direct URL to full-size image"
          },
          "thumbnailUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL to thumbnail version"
          },
          "source": {
            "type": "string",
            "description": "Source website domain"
          },
          "link": {
            "type": "string",
            "format": "uri",
            "description": "URL of page containing the image"
          }
        },
        "additionalProperties": true
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "detail"
        ],
        "properties": {
          "detail": {
            "type": "object",
            "required": [
              "error_type",
              "error_code",
              "message"
            ],
            "properties": {
              "error_type": {
                "type": "string",
                "enum": [
                  "validation_error",
                  "authentication_error",
                  "provider_error",
                  "rate_limit",
                  "internal_error",
                  "scraping_error",
                  "search_error",
                  "map_error"
                ],
                "description": "Category of error"
              },
              "error_code": {
                "type": "string",
                "description": "Specific error code for programmatic handling",
                "example": "missing_query"
              },
              "message": {
                "type": "string",
                "description": "Human-readable error message"
              },
              "details": {
                "type": "object",
                "additionalProperties": true,
                "nullable": true,
                "description": "Additional error context and debugging information"
              }
            }
          }
        }
      },
      "ExtractRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "The page URL to extract from. Must be http(s). PDF URLs are not supported — use /get_pdf_content instead.",
            "example": "https://www.ycombinator.com/blog"
          },
          "modes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "json",
                "summary",
                "qa",
                "links",
                "brand"
              ]
            },
            "default": [
              "json"
            ],
            "description": "Extraction modes to run in one call. Any combination; duplicates are ignored. Pricing is summed per mode: json/summary/qa $0.005 each, links $0.001, brand $0.002.",
            "example": [
              "json",
              "summary"
            ]
          },
          "json_schema": {
            "description": "Required when modes includes 'json'. Accepts a formal JSON schema, an example object (e.g. {\"title\": \"string\", \"price\": \"number\"}), or a plain-text description of the fields you want.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object"
              }
            ]
          },
          "query": {
            "type": "string",
            "description": "The question to answer from the page — required when modes includes 'qa'."
          },
          "instructions": {
            "type": "string",
            "description": "Optional extra guidance applied to all AI modes (json, summary, qa). E.g. 'dates in DD/MM/YYYY format'."
          },
          "response_language": {
            "type": "string",
            "default": "auto",
            "description": "Output language for summary/qa ('auto' matches the user/page language). E.g. 'en', 'fr', 'es'."
          },
          "advanced_proxy": {
            "type": "boolean",
            "default": false,
            "description": "Use advanced proxy for sites with bot protection. Adds $0.004 once per request, only when a scrape runs (brand-only requests never scrape)."
          },
          "main_content_only": {
            "type": "boolean",
            "description": "Omit this field to let the API pick the best default per selection: links-only requests scrape the full page (nav/footer links matter), AI modes use main content only."
          }
        }
      },
      "ExtractLink": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Absolute URL of the link."
          },
          "text": {
            "type": "string",
            "description": "Anchor text of the link (may be empty)."
          },
          "internal": {
            "type": "boolean",
            "description": "True when the link points to the same site (including subdomains)."
          }
        }
      },
      "ExtractResponse": {
        "type": "object",
        "description": "All five result fields are always present; modes you did not request are null. The json mode result is returned as structured_data.",
        "properties": {
          "url": {
            "type": "string",
            "description": "Final URL after redirects."
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Page title."
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "description": "Page metadata (description, OpenGraph fields, language, ...) as found by the scraper."
          },
          "structured_data": {
            "type": "object",
            "nullable": true,
            "description": "Structured data matching your schema (when 'json' in modes)."
          },
          "summary": {
            "type": "string",
            "nullable": true,
            "description": "Markdown summary of the page (when 'summary' in modes)."
          },
          "answer": {
            "type": "string",
            "nullable": true,
            "description": "Markdown answer to your question (when 'qa' in modes)."
          },
          "links": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/ExtractLink"
            },
            "description": "All links found on the page, deduplicated, max 500 (when 'links' in modes)."
          },
          "brand": {
            "type": "object",
            "nullable": true,
            "description": "Brand profile: domain, title, description, colors, logos, backdrops, socials, industries, key links and pages (when 'brand' in modes)."
          },
          "cost": {
            "type": "number",
            "description": "Total cost in USD — sum of the selected modes (+$0.004 advanced proxy when a scrape runs).",
            "example": 0.01
          },
          "response_time": {
            "type": "string",
            "description": "Total processing time in seconds.",
            "example": "3.42"
          },
          "statusCode": {
            "type": "integer",
            "example": 200
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request parameters",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "missing_query": {
                "value": {
                  "detail": {
                    "error_type": "validation_error",
                    "error_code": "missing_query",
                    "message": "Query parameter cannot be empty"
                  }
                }
              },
              "missing_json_schema": {
                "value": {
                  "detail": {
                    "error_type": "validation_error",
                    "error_code": "missing_json_schema",
                    "message": "JSON schema is required for JSON response type"
                  }
                }
              },
              "invalid_max_pages": {
                "value": {
                  "detail": {
                    "error_type": "validation_error",
                    "error_code": "invalid_max_pages",
                    "message": "max_pages must be between 1 and 100"
                  }
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Authentication error - missing or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "detail": {
                "error_type": "authentication_error",
                "error_code": "missing_llmlayer_api_key",
                "message": "Provide LLMLayer API key via 'Authorization: Bearer <token>'"
              }
            }
          }
        }
      },
      "RateLimitError": {
        "description": "Rate limit exceeded or insufficient credits",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "provider_rate_limit": {
                "value": {
                  "detail": {
                    "error_type": "provider_error",
                    "error_code": "openai_rate_limit",
                    "message": "Rate limit exceeded for openai. Please try again later",
                    "details": {
                      "provider": "openai"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "TimeoutError": {
        "description": "Operation timeout (504 Gateway Timeout)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "detail": {
                "error_type": "scraping_error",
                "error_code": "pdf_scrape_timeout",
                "message": "PDF scraping exceeded the 15-second limit",
                "details": {
                  "url": "https://www.ycombinator.com/blog/content/files/2024/06/RemedyFest-Final-Report.pdf",
                  "timeout_seconds": 15
                }
              }
            }
          }
        }
      },
      "ProviderError": {
        "description": "LLM provider error (authentication, rate limit, or general provider failure)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "auth_error": {
                "value": {
                  "detail": {
                    "error_type": "provider_error",
                    "error_code": "openai_auth_error",
                    "message": "Authentication failed for openai. Please check your provider API key",
                    "details": {
                      "provider": "openai"
                    }
                  }
                }
              },
              "provider_error": {
                "value": {
                  "detail": {
                    "error_type": "provider_error",
                    "error_code": "openai_500",
                    "message": "Error from openai provider",
                    "details": {
                      "provider": "openai",
                      "status": 500,
                      "model": "openai/gpt-4o-mini"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "unexpected_error": {
                "value": {
                  "detail": {
                    "error_type": "internal_error",
                    "error_code": "unexpected_error",
                    "message": "An unexpected error occurred. Please try again later"
                  }
                }
              },
              "scraping_error": {
                "value": {
                  "detail": {
                    "error_type": "scraping_error",
                    "error_code": "url_scrape_failed",
                    "message": "Failed to scrape content from the provided URL",
                    "details": {
                      "url": "https://www.ycombinator.com",
                      "error": "Connection timeout"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Bearer token authentication using your LLMLayer API key. Include in Authorization header as: Bearer YOUR_LLMLAYER_API_KEY"
      }
    }
  },
  "tags": [
    {
      "name": "Answer API",
      "description": "Web-enhanced AI responses combining search with multiple LLM models. Supports streaming, custom prompts, and structured JSON output."
    },
    {
      "name": "Web Search",
      "description": "Direct web search across multiple content types (general, news, shopping, videos, images, scholar) without AI processing."
    },
    {
      "name": "Content Extraction",
      "description": "Extract content from websites, PDFs, and YouTube videos in multiple formats."
    },
    {
      "name": "Web Crawling",
      "description": "Map websites and crawl multiple pages with depth control. Supports streaming with markdown output."
    }
  ]
}
