{"openapi":"3.1.0","info":{"title":"SCOUTS-AI Search API","description":"Web search backed by SearXNG. Designed for AI agents and LLM pipelines. Free while it lasts, no SLA, no auth. See /legal/terms, /legal/privacy, /legal/support.","contact":{"name":"SCOUTS-AI","url":"https://scouts-ai.com/legal/support"},"license":{"name":"Proprietary"},"version":"0.0.1-SNAPSHOT"},"servers":[{"url":"https://scouts-ai.com","description":"Production"}],"tags":[{"name":"system","description":"Operational endpoints (intentionally unauthenticated)"},{"name":"search","description":"Web search backed by SearXNG"}],"paths":{"/system/metrics":{"get":{"tags":["system"],"summary":"Read process metrics","description":"In-memory counters and latency percentiles. Lost on restart. Intended for human inspection (`curl`) or simple scraping; not Prometheus format.","operationId":"metrics","responses":{"200":{"description":"Metrics snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricsResponse"}}}}}}},"/api/search":{"get":{"tags":["search"],"summary":"Run a web search","description":"Returns up to 10 results per page, normalized for LLM consumption. Response includes cache metadata in headers (Cache-Control, X-Cache, X-Cache-TTL).","operationId":"search","parameters":[{"name":"q","in":"query","description":"Search query, 1-512 chars, trimmed and lowercased","required":true,"schema":{"type":"string"},"example":"latest llm benchmarks"},{"name":"lang","in":"query","description":"BCP-47-like language tag for results, or 'all' to search across all languages","required":false,"schema":{"type":"string","default":"en"},"example":"en"},{"name":"page","in":"query","description":"Page number, 1-10","required":false,"schema":{"type":"integer","format":"int32","default":1},"example":1}],"responses":{"200":{"description":"Search results","headers":{"X-Cache":{"description":"`HIT` if served from cache, `MISS` otherwise","style":"simple","schema":{"type":"string","enum":["HIT","MISS"]}},"X-RateLimit-Remaining":{"description":"Tokens left in the bucket after this request","style":"simple","schema":{"type":"integer"}},"Cache-Control":{"description":"Cache directive, e.g. `max-age=3600, private`","style":"simple","schema":{"type":"string"}},"X-Cache-TTL":{"description":"Seconds until the cached entry expires","style":"simple","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Bucket capacity for the calling IP","style":"simple","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"400":{"description":"Invalid query parameters","headers":{"X-RateLimit-Remaining":{"style":"simple","schema":{"type":"integer"}},"X-RateLimit-Limit":{"style":"simple","schema":{"type":"integer"}}},"content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Per-IP rate limit exceeded","headers":{"X-RateLimit-Remaining":{"style":"simple","schema":{"type":"integer","enum":["0"]}},"Retry-After":{"description":"Seconds until a token is available","style":"simple","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Epoch seconds when a token will be available","style":"simple","schema":{"type":"integer"}},"X-RateLimit-Limit":{"style":"simple","schema":{"type":"integer"}}},"content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Upstream search backend unavailable","headers":{"X-RateLimit-Remaining":{"style":"simple","schema":{"type":"integer"}},"X-RateLimit-Limit":{"style":"simple","schema":{"type":"integer"}}},"content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/health":{"get":{"tags":["health-controller"],"operationId":"health","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}}}}},"components":{"schemas":{"Latency":{"type":"object","properties":{"p50":{"type":"integer","format":"int64","description":"50th percentile in milliseconds"},"p95":{"type":"integer","format":"int64","description":"95th percentile in milliseconds"},"samples":{"type":"integer","format":"int32","description":"Sample count in the ring buffer"}}},"MetricsResponse":{"type":"object","description":"Process metrics, in-memory, lost on restart","properties":{"uptimeSeconds":{"type":"integer","format":"int64","description":"Seconds since the process started"},"rateLimit":{"$ref":"#/components/schemas/RateLimitConfig","description":"Configuration of the rate limiter at the time of the snapshot"},"totals":{"$ref":"#/components/schemas/Totals"},"cacheHitRate":{"type":"number","format":"double","description":"cacheHits / (cacheHits + cacheMisses); 0.0 when no cache decisions recorded"},"latencyMs":{"$ref":"#/components/schemas/Latency"},"perPath":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/Latency"},"description":"Per-path aggregates for the same samples as latencyMs"}}},"RateLimitConfig":{"type":"object","properties":{"enabled":{"type":"boolean"},"capacity":{"type":"integer","format":"int32"},"refillPerMinute":{"type":"integer","format":"int32"}}},"Totals":{"type":"object","properties":{"requests":{"type":"integer","format":"int64"},"status4xx":{"type":"integer","format":"int64"},"status5xx":{"type":"integer","format":"int64"},"rateLimited":{"type":"integer","format":"int64"},"cacheHits":{"type":"integer","format":"int64"},"cacheMisses":{"type":"integer","format":"int64"},"upstreamErrors":{"type":"integer","format":"int64"}}},"SearchResponse":{"type":"object","description":"Search response with metadata","properties":{"query":{"type":"string","description":"Normalized query the cache was keyed by"},"lang":{"type":"string","description":"Language used for this response (normalized lowercase)"},"page":{"type":"integer","format":"int32","description":"Page number returned","example":1},"pageSize":{"type":"integer","format":"int32","description":"Maximum results per page, always 10","example":10},"cached":{"type":"boolean","description":"True if served from the in-memory cache"},"tookMs":{"type":"integer","format":"int64","description":"Server-measured request time in milliseconds"},"results":{"type":"array","description":"Up to 10 results for this page","items":{"$ref":"#/components/schemas/SearchResult"}}}},"SearchResult":{"type":"object","description":"Single search result","properties":{"title":{"type":"string","description":"Page title","example":"Example result title"},"url":{"type":"string","description":"Result URL","example":"https://example.com/article"},"content":{"type":"string","description":"Short snippet from the page"},"publishedAt":{"type":"string","format":"date-time","description":"ISO-8601 publication timestamp from the upstream engine, or null","example":"2024-05-12T08:14:00Z"},"engine":{"type":"string","description":"SearXNG engine that produced this result, or null","example":"google"}}},"ApiError":{"type":"object","description":"Standard error envelope","properties":{"error":{"$ref":"#/components/schemas/Detail"}}},"Detail":{"type":"object","description":"Error detail","properties":{"code":{"type":"string","description":"Machine-readable code","example":"BAD_REQUEST"},"message":{"type":"string","description":"Human-readable explanation"}}}}}}