{"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"]}}}}