{
  "openapi": "3.1.0",
  "info": {
    "title": "Andi AI Search API",
    "version": "1.7.0",
    "description": "Web search and page-fetch API for people and AI agents. `GET`/`POST /api/v1/search` returns structured results with query operators, domain and date filtering, and multiple output formats. `GET /api/v1/fetch` retrieves and extracts a single page. `GET /api/v1/news/:topic` returns a curated, ranked news feed for one of 27 fixed topics, with an optional `q` keyword filter. Search and fetch support `format=context`, which returns markdown suited to language models; the news endpoint also returns RSS and an HTML widget. By default (`searchMode=auto`) the API sets the search effort per query — compute, models, and depth — automatically; the fixed modes (`low-cost`, `fast`, `balanced`, `deep`, `exhaustive`) pin an effort level when you want the same behavior on every call. Requests are billed on outcome-based pricing, reported per call in `metrics.cost_dollars`."
  },
  "servers": [
    {
      "url": "https://api.andiai.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/api/v1/search": {
      "get": {
        "operationId": "search",
        "summary": "Search the web",
        "description": "Performs a web search and returns structured results. Search effort is set automatically per query by default (`searchMode=auto`), with fixed modes (low-cost, fast, balanced, deep, exhaustive) available for manual control. Supports domain and date filtering, query operators, and multiple output formats.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search query string. Supports query operators when `parseOperators` is enabled. You can also pass a JSON array of up to 5 queries for multi-query searches (e.g., `[\"query one\", \"query two\"]`).",
            "schema": {
              "type": "string"
            },
            "example": "best programming languages"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of results to return.",
            "schema": {
              "type": "integer",
              "default": 10,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of results to skip for pagination.",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          },
          {
            "name": "searchMode",
            "in": "query",
            "description": "Search effort control. `auto` (default) sets the effort per query automatically — simple lookups resolve fast, complex questions get deeper treatment. The fixed modes pin an effort level: `low-cost` caps the work done per request for budget-constrained callers. `fast` returns results in about 1.2 seconds. `balanced` is everyday web search, about 3–4 seconds. `deep` searches from multiple angles with spell correction, about 6.5 seconds. `exhaustive` is Andi's most thorough mode: more sources and more time, up to about 15 seconds; for a question with several parts or steps it searches each part. Deeper modes add query expansion, follow-up searches fanned out from initial results, and stronger reranking. Requests are billed on outcome-based pricing (see `metrics.cost_dollars`).",
            "schema": {
              "type": "string",
              "enum": [
                "auto",
                "low-cost",
                "fast",
                "balanced",
                "deep",
                "exhaustive"
              ],
              "default": "auto"
            }
          },
          {
            "name": "effort",
            "in": "query",
            "description": "Thoroughness dial, independent of `searchMode`: `low` favors speed, `medium` is balanced, `high` searches from multiple angles with spell correction, `max` gives the most complete results and also searches each part of a question that has several parts or steps — the same tiers as pinning `searchMode` to `fast`, `balanced`, `deep`, and `exhaustive` respectively. An explicit `searchMode` always wins over `effort`. With the default `auto` mode, an explicit `effort` pins its equivalent mode. Omit for the adaptive default. Invalid values return a `400` listing the valid tiers.",
            "schema": {
              "type": "string",
              "enum": [
                "low",
                "medium",
                "high",
                "max"
              ]
            }
          },
          {
            "name": "reranker",
            "in": "query",
            "description": "Semantic reranker size hint. `small` is the lightest and fastest; `medium` and above apply stronger relevance scoring but need a deeper `searchMode` and may be clamped by the request's time and cost budget (for example, `xl` needs `deep` or `exhaustive`). `auto` (default) selects a tier automatically based on the query and search mode.",
            "schema": {
              "type": "string",
              "enum": [
                "auto",
                "small",
                "medium",
                "large",
                "xl"
              ],
              "default": "auto"
            }
          },
          {
            "name": "intent",
            "in": "query",
            "description": "Force a specific search intent. When not set, the API auto-detects intent from the query. Accepts common aliases like `search`, `news`, `video`, `images`, `weather`, `wiki`, `code`, `recipe`, `place`, `time`, and others. Set `none` to disable intent detection entirely; `auto` is equivalent to omitting the parameter.",
            "schema": {
              "type": "string"
            },
            "examples": {
              "news": {
                "value": "news",
                "summary": "Latest news results"
              },
              "video": {
                "value": "video",
                "summary": "Video search results"
              },
              "images": {
                "value": "images",
                "summary": "Image search results"
              },
              "weather": {
                "value": "weather",
                "summary": "Weather data"
              },
              "wiki": {
                "value": "wiki",
                "summary": "Wikipedia/knowledge results"
              },
              "code": {
                "value": "code",
                "summary": "Programming-related results"
              },
              "recipe": {
                "value": "recipe",
                "summary": "Recipe results"
              },
              "place": {
                "value": "place",
                "summary": "Business/place results"
              },
              "time": {
                "value": "time",
                "summary": "Time query results"
              }
            }
          },
          {
            "name": "format",
            "in": "query",
            "description": "Response format. `json` returns a JSON object. `context` returns results as markdown text, suitable for passing to language models.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "context"
              ],
              "default": "json"
            }
          },
          {
            "name": "metadata",
            "in": "query",
            "description": "Level of metadata to include. `basic` includes standard search metrics. `full` includes extended metadata with content type details and reader data on results.",
            "schema": {
              "type": "string",
              "enum": [
                "basic",
                "full"
              ],
              "default": "basic"
            }
          },
          {
            "name": "extracts",
            "in": "query",
            "description": "Include text extracts from result pages in the response. Defaults to `true` when `format=context`, `content=true` or `content=focused`; pass `extracts=false` to turn them off in those cases. Also returned by default with `metadata=full`.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "content",
            "in": "query",
            "description": "Include each result's full page text, in `reader.markdown` (or `reader.content` when the page has no markdown version). `true` returns the head of each page. `focused` returns the parts of each page that answer the query, in page order under section headings; it is billed and capped like `true` and falls back to the normal page text when no focused text can be built. Any other value is treated as `false`. Also turns on `extracts` unless `extracts=false` is passed. Adds time and cost; deeper modes allow more time.",
            "schema": {
              "type": "string",
              "enum": ["true", "focused", "false"],
              "default": "false"
            }
          },
          {
            "name": "enrichContent",
            "in": "query",
            "deprecated": true,
            "description": "Deprecated alias of `content`. Retained for backward compatibility; use `content` instead.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "maxContentLength",
            "in": "query",
            "description": "Maximum number of characters of enriched content to return per result. Only applies when `content` (or its deprecated alias `enrichContent`) is true.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "timeout",
            "in": "query",
            "description": "Target time in milliseconds for ranked results (1000–30000). It is a target, not a hard ceiling. Omit it for the mode's normal speed; a larger value (e.g. 30000) lets slower sources run longer, so deep/exhaustive searches can take close to that long. The search may run a little past it when it needs a moment more to return a useful page. Requested page text (`content`/`extracts`) is fetched afterward, on top of the target. Values below 1000 are raised to 1000; values above 30000 are lowered to 30000; 0, empty or non-numeric values are ignored. With `searchMode=auto` (the default), a timeout steps the resolved mode down to the richest one that fits it. An explicit `searchMode` is always honored.",
            "schema": {
              "type": "integer",
              "minimum": 1000,
              "maximum": 30000
            }
          },
          {
            "name": "safe",
            "in": "query",
            "description": "Safe search filtering level. `off` disables filtering, `moderate` filters most explicit content, `strict` applies maximum filtering.",
            "schema": {
              "type": "string",
              "enum": [
                "off",
                "moderate",
                "strict"
              ],
              "default": "off"
            }
          },
          {
            "name": "country",
            "in": "query",
            "description": "Two-letter ISO 3166-1 country code to localize results.",
            "schema": {
              "type": "string",
              "default": "US"
            },
            "example": "US"
          },
          {
            "name": "sourceCountry",
            "in": "query",
            "description": "Two-letter ISO 3166-1 country code to restrict results to sources from this country. Unlike `country`, which biases results by locale, `sourceCountry` excludes results from other countries.",
            "schema": {
              "type": "string"
            },
            "example": "GB"
          },
          {
            "name": "language",
            "in": "query",
            "description": "Preferred language for results, as an ISO 639-1 code.",
            "schema": {
              "type": "string",
              "default": "en"
            },
            "example": "en"
          },
          {
            "name": "units",
            "in": "query",
            "description": "Unit system for weather and calculation results. Defaults based on the `country` parameter — `imperial` for `US`, `metric` for all other countries.",
            "schema": {
              "type": "string",
              "enum": [
                "metric",
                "imperial"
              ]
            }
          },
          {
            "name": "noCache",
            "in": "query",
            "description": "Bypass cached results and fetch fresh data.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "dateRange",
            "in": "query",
            "description": "Relative date range filter for results.",
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "week",
                "month",
                "year",
                "24h",
                "7d",
                "30d",
                "90d",
                "1y"
              ]
            }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "description": "Filter results published on or after this date. Format: `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2025-01-01"
          },
          {
            "name": "dateTo",
            "in": "query",
            "description": "Filter results published on or before this date. Format: `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2025-12-31"
          },
          {
            "name": "includeDomains",
            "in": "query",
            "description": "Comma-separated list of domains to restrict results to. Supports wildcards (`*.example.com`).",
            "schema": {
              "type": "string"
            },
            "example": "github.com,stackoverflow.com"
          },
          {
            "name": "excludeDomains",
            "in": "query",
            "description": "Comma-separated list of domains to exclude from results. Supports wildcards.",
            "schema": {
              "type": "string"
            },
            "example": "pinterest.com,reddit.com"
          },
          {
            "name": "includeTerms",
            "in": "query",
            "description": "Comma-separated terms that must appear in results.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "excludeTerms",
            "in": "query",
            "description": "Comma-separated terms to exclude from results.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parseOperators",
            "in": "query",
            "description": "Parse query operators (like `site:`, `filetype:`, `intitle:`) from the query string. When disabled, the query is treated as literal text.",
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "linkFormat",
            "in": "query",
            "description": "Field name for result URLs. Use `url` to receive the URL field as `url` instead of the default `link`. Only affects the `/search` endpoint.",
            "schema": {
              "type": "string",
              "enum": [
                "link",
                "url"
              ],
              "default": "link"
            }
          },
          {
            "name": "imageFormat",
            "in": "query",
            "description": "Field naming for image results. `long` (default) uses full field names. `short` uses abbreviated field names.",
            "schema": {
              "type": "string",
              "enum": [
                "short",
                "long"
              ],
              "default": "long"
            }
          },
          {
            "name": "filetype",
            "in": "query",
            "description": "Filter results by file type.",
            "schema": {
              "type": "string"
            },
            "example": "pdf"
          },
          {
            "name": "intitle",
            "in": "query",
            "description": "Filter results to pages with this term in the title.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "inurl",
            "in": "query",
            "description": "Filter results to pages with this term in the URL.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "intext",
            "in": "query",
            "description": "Filter results to pages with this term in the body text.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results. JSON by default; LLM-ready markdown with YAML frontmatter when `format=context`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Markdown results with YAML frontmatter, returned when `format=context`."
                }
              }
            }
          },
          "400": {
            "description": "Bad request — missing or invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Missing required parameter: q",
                  "message": "The q parameter is required"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "message": "Invalid API key"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits — account balance depleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Insufficient Credits",
                  "message": "Your account has insufficient credits. Please add credits to continue."
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Too Many Requests",
                  "message": "Rate limit of N requests per second exceeded"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal server error",
                  "message": "Unknown error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "searchPost",
        "summary": "Search the web (JSON body)",
        "description": "Same contract as `GET /api/v1/search`, but accepts parameters as a JSON body. Preferred for location-bearing requests because GET-style location encoding is awkward and keeps `latitude`/`longitude` out of HTTP access logs. All GET query parameters are accepted as body fields, plus optional location fields (`latitude`, `longitude`, `accuracy`, `city`, `state`, `countryCode`, `postalCode`, `timezone`, `location`).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "q"
                ],
                "properties": {
                  "q": {
                    "type": "string",
                    "description": "Search query string. Same semantics as the GET `q` parameter."
                  },
                  "limit": {
                    "type": "integer",
                    "default": 10,
                    "minimum": 1,
                    "maximum": 100,
                    "description": "Maximum number of results to return."
                  },
                  "offset": {
                    "type": "integer",
                    "default": 0,
                    "minimum": 0,
                    "description": "Number of results to skip for pagination."
                  },
                  "searchMode": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "low-cost",
                      "fast",
                      "balanced",
                      "deep",
                      "exhaustive"
                    ],
                    "default": "auto",
                    "description": "Search effort control. `auto` (default) sets the effort per query automatically — simple lookups resolve fast, complex questions get deeper treatment. The fixed modes pin an effort level: `low-cost` caps the work done per request for budget-constrained callers. `fast` returns results in about 1.2 seconds. `balanced` is everyday web search, about 3–4 seconds. `deep` searches from multiple angles with spell correction, about 6.5 seconds. `exhaustive` is Andi's most thorough mode: more sources and more time, up to about 15 seconds; for a question with several parts or steps it searches each part. Deeper modes add query expansion, follow-up searches fanned out from initial results, and stronger reranking. Requests are billed on outcome-based pricing (see `metrics.cost_dollars`)."
                  },
                  "effort": {
                    "type": "string",
                    "enum": [
                      "low",
                      "medium",
                      "high",
                      "max"
                    ],
                    "description": "Thoroughness dial, independent of `searchMode`: `low` favors speed, `medium` is balanced, `high` searches from multiple angles with spell correction, `max` gives the most complete results and also searches each part of a question that has several parts or steps — the same tiers as pinning `searchMode` to `fast`, `balanced`, `deep`, and `exhaustive` respectively. An explicit `searchMode` always wins over `effort`. With the default `auto` mode, an explicit `effort` pins its equivalent mode. Omit for the adaptive default. Invalid values return a `400` listing the valid tiers."
                  },
                  "reranker": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "small",
                      "medium",
                      "large",
                      "xl"
                    ],
                    "default": "auto",
                    "description": "Semantic reranker size hint. `small` is the lightest and fastest; `medium` and above apply stronger relevance scoring but need a deeper `searchMode` and may be clamped by the request's time and cost budget (for example, `xl` needs `deep` or `exhaustive`). `auto` (default) selects a tier automatically based on the query and search mode."
                  },
                  "intent": {
                    "type": "string",
                    "description": "Force a specific search intent. When not set, the API auto-detects intent from the query. Accepts common aliases like `search`, `news`, `video`, `images`, `weather`, `wiki`, `code`, `recipe`, `place`, `time`, and others."
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "json",
                      "context"
                    ],
                    "default": "json",
                    "description": "Response format. `json` returns a JSON object. `context` returns results as markdown text, suitable for passing to language models."
                  },
                  "metadata": {
                    "type": "string",
                    "enum": [
                      "basic",
                      "full"
                    ],
                    "default": "basic",
                    "description": "Level of metadata to include. `basic` includes standard search metrics. `full` includes extended metadata with content type details and reader data on results."
                  },
                  "extracts": {
                    "type": "boolean",
                    "default": false,
                    "description": "Include text extracts from result pages in the response. Defaults to `true` when `format=context`, `content=true` or `content=focused`; pass `extracts=false` to turn them off in those cases. Also returned by default with `metadata=full`."
                  },
                  "content": {
                    "oneOf": [
                      { "type": "boolean" },
                      { "type": "string", "enum": ["true", "focused", "false"] }
                    ],
                    "default": false,
                    "description": "Include each result's full page text, in `reader.markdown` (or `reader.content` when the page has no markdown version). `true` returns the head of each page. `\"focused\"` returns the parts of each page that answer the query, in page order under section headings; it is billed and capped like `true` and falls back to the normal page text when no focused text can be built. Any other value is treated as `false`. Also turns on `extracts` unless `extracts=false` is passed. Adds time and cost; deeper modes allow more time."
                  },
                  "enrichContent": {
                    "type": "boolean",
                    "default": false,
                    "deprecated": true,
                    "description": "Deprecated alias of `content`."
                  },
                  "maxContentLength": {
                    "type": "integer",
                    "description": "Maximum number of characters of page text to return per result. Only applies when `content` is true."
                  },
                  "timeout": {
                    "type": "integer",
                    "minimum": 1000,
                    "maximum": 30000,
                    "description": "Target time in milliseconds for ranked results (1000–30000). It is a target, not a hard ceiling. Omit it for the mode's normal speed; a larger value (e.g. 30000) lets slower sources run longer, so deep/exhaustive searches can take close to that long. The search may run a little past it when it needs a moment more to return a useful page. Requested page text (`content`/`extracts`) is fetched afterward, on top of the target. Values below 1000 are raised to 1000; values above 30000 are lowered to 30000; 0, empty or non-numeric values are ignored. With `searchMode=auto` (the default), a timeout steps the resolved mode down to the richest one that fits it. An explicit `searchMode` is always honored."
                  },
                  "safe": {
                    "type": "string",
                    "enum": [
                      "off",
                      "moderate",
                      "strict"
                    ],
                    "default": "off",
                    "description": "Safe search filtering level. `off` disables filtering, `moderate` filters most explicit content, `strict` applies maximum filtering."
                  },
                  "country": {
                    "type": "string",
                    "default": "US",
                    "description": "Two-letter ISO 3166-1 country code to localize results."
                  },
                  "sourceCountry": {
                    "type": "string",
                    "description": "Two-letter ISO 3166-1 country code to restrict results to sources from this country. Unlike `country`, which biases results by locale, `sourceCountry` excludes results from other countries."
                  },
                  "language": {
                    "type": "string",
                    "default": "en",
                    "description": "Preferred language for results, as an ISO 639-1 code."
                  },
                  "units": {
                    "type": "string",
                    "enum": [
                      "metric",
                      "imperial"
                    ],
                    "description": "Unit system for weather and calculation results. Defaults based on the `country` parameter — `imperial` for `US`, `metric` for all other countries."
                  },
                  "noCache": {
                    "type": "boolean",
                    "default": false,
                    "description": "Bypass cached results and fetch fresh data."
                  },
                  "dateRange": {
                    "type": "string",
                    "enum": [
                      "day",
                      "week",
                      "month",
                      "year",
                      "24h",
                      "7d",
                      "30d",
                      "90d",
                      "1y"
                    ],
                    "description": "Relative date range filter for results."
                  },
                  "dateFrom": {
                    "type": "string",
                    "format": "date",
                    "description": "Filter results published on or after this date. Format: `YYYY-MM-DD`."
                  },
                  "dateTo": {
                    "type": "string",
                    "format": "date",
                    "description": "Filter results published on or before this date. Format: `YYYY-MM-DD`."
                  },
                  "includeDomains": {
                    "type": "string",
                    "description": "Comma-separated list of domains to restrict results to. Supports wildcards (`*.example.com`)."
                  },
                  "excludeDomains": {
                    "type": "string",
                    "description": "Comma-separated list of domains to exclude from results. Supports wildcards."
                  },
                  "includeTerms": {
                    "type": "string",
                    "description": "Comma-separated terms that must appear in results."
                  },
                  "excludeTerms": {
                    "type": "string",
                    "description": "Comma-separated terms to exclude from results."
                  },
                  "parseOperators": {
                    "type": "boolean",
                    "default": true,
                    "description": "Parse query operators (like `site:`, `filetype:`, `intitle:`) from the query string. When disabled, the query is treated as literal text."
                  },
                  "linkFormat": {
                    "type": "string",
                    "enum": [
                      "link",
                      "url"
                    ],
                    "default": "link",
                    "description": "Field name for result URLs. Use `url` to receive the URL field as `url` instead of the default `link`. Only affects the `/search` endpoint."
                  },
                  "imageFormat": {
                    "type": "string",
                    "enum": [
                      "short",
                      "long"
                    ],
                    "default": "long",
                    "description": "Field naming for image results. `long` (default) uses full field names. `short` uses abbreviated field names."
                  },
                  "filetype": {
                    "type": "string",
                    "description": "Filter results by file type."
                  },
                  "intitle": {
                    "type": "string",
                    "description": "Filter results to pages with this term in the title."
                  },
                  "inurl": {
                    "type": "string",
                    "description": "Filter results to pages with this term in the URL."
                  },
                  "intext": {
                    "type": "string",
                    "description": "Filter results to pages with this term in the body text."
                  },
                  "latitude": {
                    "type": "number",
                    "description": "Latitude in decimal degrees. Sent in the body to keep coordinates out of HTTP access logs."
                  },
                  "longitude": {
                    "type": "number",
                    "description": "Longitude in decimal degrees."
                  },
                  "accuracy": {
                    "type": "number",
                    "description": "Location accuracy in meters."
                  },
                  "city": {
                    "type": "string",
                    "description": "City name for location-aware queries."
                  },
                  "state": {
                    "type": "string",
                    "description": "State or region name."
                  },
                  "countryCode": {
                    "type": "string",
                    "description": "Two-letter ISO 3166-1 country code for the caller's location."
                  },
                  "postalCode": {
                    "type": "string",
                    "description": "Postal or ZIP code."
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA timezone, for example America/Los_Angeles."
                  },
                  "location": {
                    "type": "string",
                    "description": "Display name for the caller's location, for example \"San Francisco, CA\"."
                  }
                }
              },
              "example": {
                "q": "best italian restaurants near me",
                "limit": 10,
                "city": "San Francisco",
                "state": "California",
                "countryCode": "US",
                "latitude": 37.7794,
                "longitude": -122.4176
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results. JSON by default; LLM-ready markdown with YAML frontmatter when `format=context`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Markdown results with YAML frontmatter, returned when `format=context`."
                }
              }
            }
          },
          "400": {
            "description": "Bad request — missing or invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits — account balance depleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/fetch": {
      "get": {
        "summary": "Fetch a web page",
        "description": "Fetches a single web page and returns clean extracted content (title, article text, metadata) as JSON, or LLM-ready markdown with format=context. The companion to /api/v1/search: search, pick a result, fetch it in full.",
        "operationId": "fetchPage",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Full http(s) URL of the page to fetch.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Response format: json (structured, default) or context (LLM-ready markdown).",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "context"
              ],
              "default": "json"
            }
          },
          {
            "name": "effort",
            "in": "query",
            "required": false,
            "description": "How thoroughly to retrieve the page: `low` favors speed, `max` spends the most time extracting content — useful for pages that are slow to load or render content client-side. Omit for the server's adaptive default. Invalid values return a `400` listing the valid tiers. Fetch failures (`422`, `503`) are never billed, regardless of `effort`.",
            "schema": {
              "type": "string",
              "enum": [
                "low",
                "medium",
                "high",
                "max"
              ]
            }
          },
          {
            "name": "maxContentLength",
            "in": "query",
            "required": false,
            "description": "Maximum content characters to return (default 100000, maximum 200000).",
            "schema": {
              "type": "integer",
              "default": 100000,
              "minimum": 1,
              "maximum": 200000
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Optional query string to scope extraction to. When set, the response includes `query_extracts`, `query_snippet`, and `query_hash` fields scoped to this query.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Extracted page content",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "content": {
                      "type": "string",
                      "description": "Clean extracted text content"
                    },
                    "markdown": {
                      "type": "string",
                      "description": "Clean extracted content as markdown, when available."
                    },
                    "extracts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Key passage extracts from the page, when available."
                    },
                    "snippet": {
                      "type": "string",
                      "description": "Short excerpt from the page."
                    },
                    "author": {
                      "type": "string"
                    },
                    "date": {
                      "type": "string"
                    },
                    "site_name": {
                      "type": "string"
                    },
                    "image": {
                      "type": "string"
                    },
                    "lang": {
                      "type": "string"
                    },
                    "word_count": {
                      "type": "integer"
                    },
                    "truncated": {
                      "type": "boolean"
                    },
                    "query_extracts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Text extracts from the page most relevant to `query`. Only present when `query` is set."
                    },
                    "query_snippet": {
                      "type": "string",
                      "description": "Short excerpt from the page most relevant to `query`. Only present when `query` is set."
                    },
                    "query_hash": {
                      "type": "string",
                      "description": "Hash identifying the `query` used to scope this response. Only present when `query` is set."
                    },
                    "partial": {
                      "type": "boolean",
                      "description": "Whether this is a partial response returned before the page finished retrieving. Only present when true."
                    },
                    "retry_after_seconds": {
                      "type": "integer",
                      "description": "Seconds to wait before retrying for the full content. Only present on partial responses."
                    },
                    "metrics": {
                      "type": "object",
                      "properties": {
                        "duration_ms": {
                          "type": "integer"
                        },
                        "cost_dollars": {
                          "type": "number",
                          "description": "Actual cost of this fetch in USD (post-discount). Failed fetches are not billed."
                        }
                      }
                    }
                  }
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid url parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Credit balance exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The page was reached but its content could not be extracted. Not retriable. Not billed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The page is still being retrieved, or a transient failure occurred. Retriable — retry after the number of seconds in the `Retry-After` header. Not billed.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Content warming",
                  "message": "The page is still being retrieved. Retry in 5s for the full content.",
                  "retry_after_seconds": 5,
                  "url": "https://example.com/article"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/news": {
      "get": {
        "operationId": "listNewsTopics",
        "summary": "List news topics",
        "description": "Lists every topic available at `GET /api/v1/news/{topic}` with its title, category, supported formats, whether a detailed brief is available and its recent story count. Requires an API key; not billed. A feed key can be sent in the `key` query parameter. Add `format=html` for a page of ready links.",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "A feed key, for links that cannot send a header. Only feed keys are accepted here; a full API key in a URL returns `403`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "`json` (default) returns the topics list. `html` returns the same list as a page of ready links; a feed key is added to every link. Any other value returns the JSON list.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "html"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Topics index",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "topics": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string",
                            "description": "Topic slug to use in `/api/v1/news/{topic}`."
                          },
                          "title": {
                            "type": "string",
                            "description": "Plain-language description of the feed."
                          },
                          "category": {
                            "type": "string",
                            "enum": [
                              "news",
                              "region",
                              "extended",
                              "group"
                            ],
                            "description": "Section the topic belongs to."
                          },
                          "formats": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Supported `format` values."
                          },
                          "detail": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "enum": [
                                "full"
                              ]
                            },
                            "description": "`[\"full\"]` when the topic has a detailed brief (`detail=full`); otherwise empty."
                          },
                          "count_48h": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "Stories in the last 48 hours. `null` for the curated groups (`top`, `hn-frontpage`, `ai-news`, `tech-news`), where a per-topic count does not apply, and for every topic if the count is temporarily unavailable."
                          }
                        },
                        "required": [
                          "slug",
                          "title",
                          "category",
                          "formats",
                          "detail",
                          "count_48h"
                        ]
                      }
                    },
                    "hours": {
                      "type": "integer",
                      "description": "Window the counts cover, in hours."
                    },
                    "count": {
                      "type": "integer",
                      "description": "Number of topics."
                    }
                  },
                  "required": [
                    "topics",
                    "hours",
                    "count"
                  ]
                }
              },
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "A web page of ready links for every topic (RSS, widget, ticker, detailed brief). Returned only for `format=html`."
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "message": "Invalid API key"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits — account balance depleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Insufficient Credits",
                  "message": "Your account has insufficient credits. Please add credits to continue."
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Too Many Requests",
                  "message": "Rate limit of N requests per second exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/news/{topic}": {
      "get": {
        "operationId": "getNewsFeed",
        "summary": "Get a curated news feed",
        "description": "Returns a curated, ranked news feed for a fixed topic. The topic path segment selects the feed; an optional `q` filters it by keyword. `results` are ordered by relevance (a semantic pass tuned to the topic, weighted toward fresher articles); `news` holds the same articles in strict reverse-chronological order. `images` includes article images when available. When a pre-written brief exists for the topic it is returned in `aiSummary` (also in `answer`); a response to a `q` request carries no brief. Choose the output with `format`: `json` (default), `context` (markdown for LLMs), `rss` (RSS 2.0), `embed` (an HTML widget) or `ticker` (a scrolling headline bar). Links that cannot send a header, such as an `<iframe>` or a feed reader, use a feed key in the `key` query parameter with `rss`, `embed` or `ticker`. `GET /api/v1/news` lists the topics.",
        "parameters": [
          {
            "name": "topic",
            "in": "path",
            "required": true,
            "description": "Topic slug selecting the feed.",
            "schema": {
              "type": "string",
              "enum": [
                "technology",
                "business",
                "finance",
                "politics",
                "sports",
                "health",
                "science",
                "world",
                "entertainment",
                "us",
                "europe",
                "uk",
                "asia",
                "middle-east",
                "africa",
                "latin-america",
                "australia",
                "programming",
                "startups",
                "media",
                "crypto",
                "legal",
                "space",
                "top",
                "hn-frontpage",
                "ai-news",
                "tech-news"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Number of results to return. Values outside 1–50 are clamped.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Output format. `json` returns the response object. `context` returns markdown for language models (`text/markdown`), with the topic brief as a `## Summary` block. `rss` returns an RSS 2.0 feed (`application/rss+xml`), with the brief in an `andi:aiSummary` element. `embed` returns a self-contained HTML widget (`text/html`). `ticker` returns a self-contained scrolling headline bar (`text/html`). Both can be loaded in an `<iframe>` with a feed key in the `key` parameter. A feed key works only with `rss`, `embed` and `ticker`. Any other value returns `400`.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "context",
                "rss",
                "embed",
                "ticker"
              ],
              "default": "json"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Keyword filter within the topic, ranked against your query. Up to 200 characters. A response to a `q` request carries no brief (`aiSummary` is absent and `answer` is empty).",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Alias of `q`.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "hours",
            "in": "query",
            "required": false,
            "description": "Recency window in hours. Values outside 1–336 are clamped. Defaults to 48; `format=ticker` defaults to 24 unless `q` is set.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 336,
              "default": 48
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Language code (for example `en`, `es`), or `all` for every language. An unrecognised value falls back to `en`.",
            "schema": {
              "type": "string",
              "default": "en"
            }
          },
          {
            "name": "summary",
            "in": "query",
            "required": false,
            "description": "Set to `0` or `false` to leave the topic brief out of this response (`aiSummary` is dropped and `answer` is empty).",
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "detail",
            "in": "query",
            "required": false,
            "description": "Set to `full` to get the topic's detailed brief. Only topics that list `detail: [\"full\"]` in `GET /api/v1/news` have one (currently `startups` and `space`); other topics return their standard brief.",
            "schema": {
              "type": "string",
              "enum": [
                "full"
              ]
            }
          },
          {
            "name": "extracts",
            "in": "query",
            "required": false,
            "description": "Include stored text passages from each article, in `extracts[]`. Defaults to `true` for `format=context` and when `content=true`; pass `extracts=false` to turn them off. Nothing is fetched live; the text comes from pages Andi has already stored.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "content",
            "in": "query",
            "required": false,
            "description": "Include each article's stored full text in `reader.markdown`. Also turns on `extracts` unless `extracts=false` is passed. Billed per token, as on search.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "maxContentLength",
            "in": "query",
            "required": false,
            "description": "Maximum number of characters of `reader.markdown` to return per article. Only applies when `content=true`.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "metadata",
            "in": "query",
            "required": false,
            "description": "Metadata level for `format=context`. `basic` is the default.",
            "schema": {
              "type": "string",
              "enum": [
                "basic",
                "full"
              ],
              "default": "basic"
            }
          },
          {
            "name": "title",
            "in": "query",
            "required": false,
            "description": "`format=embed` only: heading shown in the widget. Up to 80 characters.",
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          },
          {
            "name": "tz",
            "in": "query",
            "required": false,
            "description": "`format=embed` only: IANA time zone for displayed times, for example `America/New_York`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "theme",
            "in": "query",
            "required": false,
            "description": "`format=ticker` only: colour theme.",
            "schema": {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ],
              "default": "light"
            }
          },
          {
            "name": "speed",
            "in": "query",
            "required": false,
            "description": "`format=ticker` only: scroll speed, from 20 to 200.",
            "schema": {
              "type": "integer",
              "minimum": 20,
              "maximum": 200,
              "default": 60
            }
          },
          {
            "name": "label",
            "in": "query",
            "required": false,
            "description": "`format=ticker` only: short text shown on the ticker's label block.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "A feed key, for links that cannot send a header (an `<iframe>`, a dashboard, a feed reader). Works only with `format=rss`, `embed` or `ticker`, and on `GET /api/v1/news`. Any other format, or no `format`, returns `403`. A full API key in a URL returns `403`: send it in the `x-api-key` header instead. Treat a feed-key link like a password.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "noCache",
            "in": "query",
            "required": false,
            "description": "Bypass the cache and fetch a fresh feed.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Curated news feed in the requested `format`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Markdown for language models (`format=context`)."
                }
              },
              "application/rss+xml": {
                "schema": {
                  "type": "string",
                  "description": "RSS 2.0 feed (`format=rss`)."
                }
              },
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "Self-contained HTML widget (`format=embed`) or scrolling ticker (`format=ticker`)."
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameter: an unknown `format` or a `q` longer than 200 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid parameter",
                  "message": "Invalid value for 'format'. Must be one of: json, context, rss, embed, ticker."
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "message": "Invalid API key"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits — account balance depleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Insufficient Credits",
                  "message": "Your account has insufficient credits. Please add credits to continue."
                }
              }
            }
          },
          "403": {
            "description": "Not allowed. Either a feed key was used for something it does not cover (another endpoint, `format=json` or `context`, or no `format`), or a full API key was put in the URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "feedKey": {
                    "summary": "Feed key used outside news feeds",
                    "value": {
                      "error": "Feed key",
                      "message": "This key only works for news feeds (RSS, widget, ticker). Use a full API key for other endpoints."
                    }
                  },
                  "keyInUrl": {
                    "summary": "Full API key in the URL",
                    "value": {
                      "error": "Key not allowed in URL",
                      "message": "A full API key must not be put in a link. Send it in the x-api-key header instead, or create a feed key in the API console (Keys → Create key → Feed key) for links."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown topic. The response includes the full list of valid topic slugs.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "valid_topics": {
                          "type": "array",
                          "description": "Every valid topic slug.",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "valid_topics"
                      ]
                    }
                  ]
                },
                "example": {
                  "error": "Unknown topic",
                  "message": "No curated news feed exists for topic 'gardening'.",
                  "valid_topics": [
                    "technology",
                    "business",
                    "finance",
                    "politics",
                    "sports",
                    "health",
                    "science",
                    "world",
                    "entertainment",
                    "us",
                    "europe",
                    "uk",
                    "asia",
                    "middle-east",
                    "africa",
                    "latin-america",
                    "australia",
                    "programming",
                    "startups",
                    "media",
                    "crypto",
                    "legal",
                    "space",
                    "top",
                    "hn-frontpage",
                    "ai-news",
                    "tech-news"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Too Many Requests",
                  "message": "Rate limit of N requests per second exceeded"
                }
              }
            }
          },
          "502": {
            "description": "The `embed` or `ticker` widget could not be produced safely. Retry shortly or use another format. For these two formats the body is a short HTML message instead of JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Bad gateway",
                  "message": "The 'world' news feed is temporarily unavailable. Retry shortly."
                }
              }
            }
          },
          "503": {
            "description": "The feed is temporarily unavailable. Retriable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Service unavailable",
                  "message": "The 'world' news feed is temporarily unavailable. Retry shortly."
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "API key from the [API Console](https://console.andiai.com)."
      }
    },
    "schemas": {
      "SearchResponse": {
        "type": "object",
        "properties": {
          "results_type": {
            "type": "string",
            "description": "The type of results returned (e.g., `Search`, `News`, `Weather`, `Calculator`). Determines which optional response sections are present."
          },
          "answer": {
            "type": "string",
            "description": "Instant answer for the query when one is found — for example a definition, a weather line or a calculation — otherwise an empty string. On news feeds, a short summary of the headlines when available."
          },
          "type": {
            "type": "string",
            "description": "Same as `results_type`. Indicates the category of results."
          },
          "title": {
            "type": "string",
            "description": "Your query text (for a news feed, the feed's description)."
          },
          "results": {
            "type": "array",
            "description": "Array of search results.",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "metrics": {
            "$ref": "#/components/schemas/Metrics",
            "description": "Search performance metrics. Always included in the response."
          },
          "correctedQuery": {
            "type": "string",
            "description": "Spell-corrected query. Present when a misspelling in the original query is detected and corrected — any mode can return it; deep and exhaustive modes run the strongest spell correction."
          },
          "related_searches": {
            "type": "array",
            "description": "Related search suggestions. Present on some queries.",
            "items": {
              "type": "string"
            }
          },
          "topics": {
            "type": "array",
            "description": "Related topics. Present on some queries.",
            "items": {
              "type": "string"
            }
          },
          "related_topics": {
            "type": "array",
            "description": "Related topic names for entity answers.",
            "items": {
              "type": "string"
            }
          },
          "infobox": {
            "type": "object",
            "description": "Facts panel for an entity answer.",
            "properties": {
              "heading": {
                "type": "string"
              },
              "source": {
                "type": "string"
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "disambiguation": {
            "type": "object",
            "description": "Possible meanings when the query is ambiguous.",
            "properties": {
              "query": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "text": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "aiSummary": {
            "type": "object",
            "description": "A pre-written news summary. News feeds return it, and so does search when the query asks for a whole feed (for example \"latest technology news\" or \"hacker news\"), but not for a topic inside a feed (\"nfl news\"). On news feeds the same text is in `answer`; on search, `answer` can hold something else, such as a description of the site.",
            "properties": {
              "text": {
                "type": "string",
                "description": "The summary, as markdown."
              },
              "generatedAt": {
                "type": "string",
                "format": "date-time",
                "description": "When the summary was written."
              },
              "model": {
                "type": "string",
                "description": "A short label for the summary's source."
              },
              "mode": {
                "type": "string",
                "enum": [
                  "pregen"
                ],
                "description": "`pregen` means the summary was written ahead of your request."
              }
            }
          },
          "safeSearch": {
            "type": "object",
            "description": "Echo of the safe-search state. Present on cache misses. Use it to confirm your `safe` parameter was honored.",
            "properties": {
              "requested": {
                "type": "string",
                "enum": [
                  "off",
                  "moderate",
                  "strict"
                ]
              },
              "applied": {
                "type": "string",
                "enum": [
                  "off",
                  "moderate",
                  "strict"
                ]
              }
            }
          },
          "videos": {
            "type": "array",
            "description": "Video results, when the search found any.",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "images": {
            "type": "array",
            "description": "Image results, when the search found any.",
            "items": {
              "$ref": "#/components/schemas/ImageResult"
            }
          },
          "news": {
            "type": "array",
            "description": "News results, when the search found any.",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "places": {
            "type": "array",
            "description": "Place and business results, when the search found any.",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "profiles": {
            "type": "array",
            "description": "People and entity profiles, when the search found any.",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "social": {
            "type": "array",
            "description": "Social posts, when the search found any.",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "academic": {
            "type": "array",
            "description": "Scholarly papers, when the search found any.",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "weather": {
            "$ref": "#/components/schemas/WeatherResult",
            "description": "Weather data. Present when the query triggers a weather intent."
          },
          "calculation": {
            "$ref": "#/components/schemas/CalculationResult",
            "description": "Calculation result. Present when the query triggers a calculation intent."
          }
        },
        "required": [
          "results_type",
          "answer",
          "type",
          "title",
          "results",
          "metrics"
        ]
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Page title."
          },
          "link": {
            "type": "string",
            "format": "uri",
            "description": "Page URL."
          },
          "desc": {
            "type": "string",
            "description": "Page description or summary."
          },
          "source": {
            "type": "string",
            "description": "Site or publisher name (for example `Wikipedia`); a name derived from the domain when none is known."
          },
          "type": {
            "type": "string",
            "description": "Result type. Only included when `metadata=full`.",
            "enum": [
              "website",
              "blog",
              "news",
              "video",
              "image",
              "place",
              "profile",
              "social",
              "academic",
              "calculation",
              "weather",
              "computation",
              "instant answer"
            ]
          },
          "date": {
            "type": "string",
            "description": "Publication date of the content, when available."
          },
          "dateModified": {
            "type": "string",
            "description": "When the page was last updated, if known."
          },
          "image": {
            "type": "string",
            "format": "uri",
            "description": "Preview image URL, when available. Only included when `metadata=full`."
          },
          "snippet": {
            "type": "string",
            "description": "Query-relevant text excerpt from the page. Distinct from `desc`, which is the general page description."
          },
          "answer": {
            "type": "string",
            "description": "Inline answer for instant answer results."
          },
          "extracts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Query-relevant passages from the page. Returned with `extracts=true`, and by default with `format=context`, `content=true` and `metadata=full`."
          },
          "contentType": {
            "type": "string",
            "description": "Content type of the page. Only included when `metadata=full`."
          },
          "reader": {
            "type": "object",
            "description": "Extracted page content and metadata. Included with `metadata=full`; with `content=true` it holds the full page text in `reader.markdown` (or `reader.content`) at any metadata level."
          },
          "contentSafety": {
            "type": "object",
            "description": "Safety classification. Only included when `metadata=full`.",
            "properties": {
              "rating": {
                "type": "string",
                "enum": [
                  "safe",
                  "unsafe",
                  "unknown"
                ],
                "description": "Content safety rating."
              },
              "safeSearchApplied": {
                "type": "boolean",
                "description": "Whether safe-search filtering was applied to this result."
              }
            }
          },
          "bang": {
            "type": "string",
            "description": "Bang shortcut for the result domain. Only included when `metadata=full`."
          },
          "queryIndices": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Multi-query only: which of your queries (0-based) found this result."
          },
          "seeAlso": {
            "type": "array",
            "description": "News feeds only: other outlets covering the same story.",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                },
                "source": {
                  "type": "string"
                }
              }
            }
          }
        },
        "required": [
          "title",
          "link",
          "desc",
          "source"
        ]
      },
      "ImageResult": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Image title or alt text."
          },
          "link": {
            "type": "string",
            "format": "uri",
            "description": "URL of the page containing the image."
          },
          "image": {
            "type": "string",
            "format": "uri",
            "description": "Direct URL of the full-size image."
          },
          "source": {
            "type": "string",
            "description": "Site or publisher name (for example `Wikipedia`); a name derived from the domain when none is known."
          },
          "type": {
            "type": "string",
            "description": "Always `image`.",
            "enum": [
              "image"
            ]
          },
          "thumbnail": {
            "type": "string",
            "format": "uri",
            "description": "Thumbnail URL."
          },
          "width": {
            "type": "string",
            "description": "Image width in pixels (as a string)."
          },
          "height": {
            "type": "string",
            "description": "Image height in pixels (as a string)."
          }
        },
        "required": [
          "title",
          "link",
          "image",
          "source",
          "type"
        ]
      },
      "WeatherResult": {
        "type": "object",
        "properties": {
          "location": {
            "type": "object",
            "description": "Location details.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Location name."
              },
              "country": {
                "type": "string",
                "description": "Country name or code."
              },
              "coordinates": {
                "type": "object",
                "properties": {
                  "latitude": {
                    "type": "number"
                  },
                  "longitude": {
                    "type": "number"
                  }
                }
              }
            }
          },
          "temperature": {
            "type": "number",
            "description": "Current temperature."
          },
          "feelsLike": {
            "type": "number",
            "description": "Feels-like temperature."
          },
          "units": {
            "type": "string",
            "description": "Unit system: `metric` or `imperial`.",
            "enum": [
              "metric",
              "imperial"
            ]
          },
          "description": {
            "type": "string",
            "description": "Weather condition description."
          },
          "humidity": {
            "type": "number",
            "description": "Humidity percentage."
          },
          "windSpeed": {
            "type": "number",
            "description": "Wind speed."
          },
          "windDirection": {
            "type": "number",
            "description": "Wind direction in degrees."
          },
          "pressure": {
            "type": "number",
            "description": "Atmospheric pressure."
          },
          "icon": {
            "type": "string",
            "description": "Weather icon identifier."
          },
          "cloudiness": {
            "type": "number",
            "description": "Cloud cover percentage."
          },
          "visibility": {
            "type": "number",
            "description": "Visibility distance."
          },
          "timestamp": {
            "type": "integer",
            "description": "Time of the reading, Unix epoch milliseconds."
          }
        },
        "required": [
          "location",
          "temperature",
          "units",
          "description"
        ]
      },
      "CalculationResult": {
        "type": "object",
        "properties": {
          "expression": {
            "type": "string",
            "description": "The mathematical expression."
          },
          "result": {
            "type": "string",
            "description": "The calculated result."
          },
          "rawResult": {
            "description": "The unformatted result (usually a number)."
          }
        },
        "required": [
          "expression",
          "result"
        ]
      },
      "Metrics": {
        "type": "object",
        "description": "Search performance metrics. Always included in the response.",
        "properties": {
          "query": {
            "type": "string",
            "description": "The query as processed."
          },
          "intent": {
            "type": "string",
            "description": "The detected or forced search intent."
          },
          "timestamp": {
            "type": "string",
            "description": "Timestamp of the search request."
          },
          "duration": {
            "type": "number",
            "description": "Total request duration in milliseconds."
          },
          "queries_executed": {
            "type": "integer",
            "description": "Number of search queries executed."
          },
          "api_requests_count": {
            "type": "integer",
            "description": "Number of queries in your request (the same as `queries_executed`)."
          },
          "results_returned": {
            "type": "integer",
            "description": "Number of results returned in this response."
          },
          "total_results_found": {
            "type": "integer",
            "description": "Rough estimate of matching pages, as reported by sources (0 for news feeds)."
          },
          "queries_provided": {
            "type": "integer",
            "description": "Number of queries you sent. Only present when more than 5 were sent."
          },
          "queries_truncated": {
            "type": "integer",
            "description": "How many queries were dropped because only the first 5 run. Only present when more than 5 were sent."
          },
          "cached": {
            "type": "boolean",
            "description": "Whether the response was served from cache. Only present on cache hits."
          },
          "cache_age_seconds": {
            "type": "integer",
            "description": "Age of the cached response in seconds. Only present on cache hits."
          },
          "cost_dollars": {
            "type": "number",
            "description": "Amount charged to your account for this request in USD, after any discounts. Pricing is outcome-based — each request is charged for the work performed and the content delivered, whether the effort was set automatically (`auto`) or pinned with a fixed mode."
          },
          "effort": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high",
              "max"
            ],
            "description": "Resolved effort tier for this request — the tier `auto` mode selected, or the one pinned via `searchMode`/`effort`."
          },
          "response_time_ms": {
            "type": "integer",
            "description": "Total server response time in milliseconds."
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Error type or message."
          },
          "message": {
            "type": "string",
            "description": "Detailed error description."
          }
        },
        "required": [
          "error",
          "message"
        ]
      }
    }
  }
}
