openapi: 3.0.3 info: title: OpenSERP API version: 2.1.0 description: > OpenSERP provides dedicated and multi-engine search endpoints for Google, Yandex, Baidu, Bing, and DuckDuckGo. Search responses are wrapped in a v2 envelope with query echo, metadata, normalized results, and pagination. Invalid client input returns 400 with a machine-readable `reason` code. license: name: MIT url: https://opensource.org/licenses/MIT servers: - url: http://127.0.0.1:7000 description: Local default server security: [] tags: - name: Search description: Dedicated per-engine search endpoints - name: Mega description: Cross-engine aggregated search endpoints - name: Health description: Health and readiness endpoints - name: Stats description: Runtime statistics endpoints - name: Docs description: OpenAPI and Swagger UI endpoints paths: /{engine}/search: get: tags: [Search] operationId: searchWeb summary: Search web results from a specific engine description: > Engine path values are `google`, `yandex`, `baidu`, `bing`, `duck`, and `ecosia` (`duck` maps to DuckDuckGo internally). Use `?format=markdown|text|ndjson` for alternative output formats. parameters: - $ref: "#/components/parameters/EnginePath" - $ref: "#/components/parameters/TextQuery" - $ref: "#/components/parameters/LangQuery" - $ref: "#/components/parameters/RegionQuery" - $ref: "#/components/parameters/DateQuery" - $ref: "#/components/parameters/FileQuery" - $ref: "#/components/parameters/SiteQuery" - $ref: "#/components/parameters/LimitQuery" - $ref: "#/components/parameters/StartQuery" - $ref: "#/components/parameters/FilterQuery" - $ref: "#/components/parameters/AnswersQuery" - $ref: "#/components/parameters/FormatQuery" - $ref: "#/components/parameters/UseProxyHeader" - $ref: "#/components/parameters/ProxyURLHeader" - $ref: "#/components/parameters/ProxyCountryHeader" - $ref: "#/components/parameters/ProxyClassHeader" - $ref: "#/components/parameters/ProxyProviderHeader" - $ref: "#/components/parameters/ProxySessionIDHeader" - $ref: "#/components/parameters/TenantHeader" responses: "200": description: Search results envelope headers: X-Request-ID: $ref: "#/components/headers/XRequestID" X-Cache: $ref: "#/components/headers/XCache" X-Fallback-Engine: $ref: "#/components/headers/XFallbackEngine" X-Proxy-Mode: $ref: "#/components/headers/XProxyMode" X-Proxy-Tag: $ref: "#/components/headers/XProxyTag" X-Proxy-Used: $ref: "#/components/headers/XProxyUsed" X-Network-Bytes: $ref: "#/components/headers/XNetworkBytes" X-Browser-Profile-Id: $ref: "#/components/headers/XBrowserProfileID" content: application/json: schema: $ref: "#/components/schemas/SearchEnvelope" examples: default: value: query: text: golang lang: EN region: US engines_requested: [google] meta: request_id: 01HXYZ... requested_at: "2026-04-24T12:00:00Z" took_ms: 842 engines_failed: [] version: "2.1" results: - id: s_a1b2c3d4e5f6a1b2 rank: 1 type: organic title: The Go Programming Language url: https://go.dev/ display_url: go.dev snippet: Go is an open source programming language... domain: go.dev favicon: https://go.dev/favicon.ico position: absolute: 1 engine: google domain_info: tld: dev sld: go category: "" pagination: page: 1 has_more: true next_start: 25 text/markdown: schema: type: string text/plain: schema: type: string application/x-ndjson: schema: type: string "400": $ref: "#/components/responses/BadRequestError" "403": $ref: "#/components/responses/ForbiddenError" "429": $ref: "#/components/responses/TooManyRequestsError" "502": $ref: "#/components/responses/BadGatewayError" "503": $ref: "#/components/responses/ServiceUnavailableError" "504": $ref: "#/components/responses/GatewayTimeoutError" "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalServerError" /{engine}/image: get: tags: [Search] operationId: searchImages summary: Search image results from a specific engine parameters: - $ref: "#/components/parameters/EnginePath" - $ref: "#/components/parameters/TextQuery" - $ref: "#/components/parameters/LangQuery" - $ref: "#/components/parameters/RegionQuery" - $ref: "#/components/parameters/DateQuery" - $ref: "#/components/parameters/FileQuery" - $ref: "#/components/parameters/SiteQuery" - $ref: "#/components/parameters/LimitQuery" - $ref: "#/components/parameters/StartQuery" - $ref: "#/components/parameters/FilterQuery" - $ref: "#/components/parameters/AnswersQuery" - $ref: "#/components/parameters/FormatQuery" - $ref: "#/components/parameters/UseProxyHeader" - $ref: "#/components/parameters/ProxyURLHeader" - $ref: "#/components/parameters/ProxyCountryHeader" - $ref: "#/components/parameters/ProxyClassHeader" - $ref: "#/components/parameters/ProxyProviderHeader" - $ref: "#/components/parameters/ProxySessionIDHeader" - $ref: "#/components/parameters/TenantHeader" responses: "200": description: Image search results envelope headers: X-Request-ID: $ref: "#/components/headers/XRequestID" X-Cache: $ref: "#/components/headers/XCache" X-Fallback-Engine: $ref: "#/components/headers/XFallbackEngine" X-Proxy-Mode: $ref: "#/components/headers/XProxyMode" X-Proxy-Tag: $ref: "#/components/headers/XProxyTag" X-Proxy-Used: $ref: "#/components/headers/XProxyUsed" X-Network-Bytes: $ref: "#/components/headers/XNetworkBytes" X-Browser-Profile-Id: $ref: "#/components/headers/XBrowserProfileID" content: application/json: schema: $ref: "#/components/schemas/ImageEnvelope" "400": $ref: "#/components/responses/BadRequestError" "403": $ref: "#/components/responses/ForbiddenError" "429": $ref: "#/components/responses/TooManyRequestsError" "502": $ref: "#/components/responses/BadGatewayError" "503": $ref: "#/components/responses/ServiceUnavailableError" "504": $ref: "#/components/responses/GatewayTimeoutError" "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalServerError" /google/parse: post: tags: [Search] operationId: parseGoogleHTML summary: Parse a Google SERP HTML document into structured results description: > Accepts raw Google SERP HTML in the request body and returns a standard search envelope. Useful when an upstream provider delivers raw HTML rather than JSON. No browser is used; parsing is done with goquery. The body size limit is 10 MB. requestBody: required: true content: text/html: schema: type: string description: Raw Google SERP HTML page parameters: - $ref: "#/components/parameters/FormatQuery" responses: "200": description: Parsed search results envelope content: application/json: schema: $ref: "#/components/schemas/SearchEnvelope" text/markdown: schema: type: string text/plain: schema: type: string application/x-ndjson: schema: type: string "400": $ref: "#/components/responses/BadRequestError" "500": $ref: "#/components/responses/InternalServerError" /bing/parse: post: tags: [Search] operationId: parseBingHTML summary: Parse a Bing SERP HTML document into structured results description: > Accepts raw Bing SERP HTML in the request body and returns a standard search envelope. Useful when an upstream provider delivers raw HTML rather than JSON. No browser is used; parsing is done with goquery. The body size limit is 10 MB. requestBody: required: true content: text/html: schema: type: string description: Raw Bing SERP HTML page parameters: - $ref: "#/components/parameters/FormatQuery" responses: "200": description: Parsed search results envelope content: application/json: schema: $ref: "#/components/schemas/SearchEnvelope" text/markdown: schema: type: string text/plain: schema: type: string application/x-ndjson: schema: type: string "400": $ref: "#/components/responses/BadRequestError" "500": $ref: "#/components/responses/InternalServerError" /mega/search: get: tags: [Mega] operationId: megaSearch summary: Search across multiple engines with selectable execution mode description: > Mode controls engine execution strategy: `balanced` (default) queries all selected engines in parallel, `any` runs engines sequentially in requested order until first success, and `fast` queries only the fastest engine based on circuit-breaker average response time stats. In `balanced` mode, `dedupe` and `merge` tune aggregation behavior. Partial failures are surfaced in `meta.engines_failed` and `meta.engine_errors`. If all selected engines fail, the endpoint returns a 502 with per-engine error details. Use `?format=markdown|text|ndjson` for alternative output formats. parameters: - $ref: "#/components/parameters/TextQuery" - $ref: "#/components/parameters/LangQuery" - $ref: "#/components/parameters/RegionQuery" - $ref: "#/components/parameters/DateQuery" - $ref: "#/components/parameters/FileQuery" - $ref: "#/components/parameters/SiteQuery" - $ref: "#/components/parameters/LimitQuery" - $ref: "#/components/parameters/StartQuery" - $ref: "#/components/parameters/FilterQuery" - $ref: "#/components/parameters/AnswersQuery" - $ref: "#/components/parameters/EnginesQuery" - $ref: "#/components/parameters/MegaModeQuery" - $ref: "#/components/parameters/MegaDedupeQuery" - $ref: "#/components/parameters/MegaMergeQuery" - $ref: "#/components/parameters/FormatQuery" - $ref: "#/components/parameters/UseProxyHeader" - $ref: "#/components/parameters/ProxyURLHeader" - $ref: "#/components/parameters/ProxyCountryHeader" - $ref: "#/components/parameters/ProxyClassHeader" - $ref: "#/components/parameters/ProxyProviderHeader" - $ref: "#/components/parameters/ProxySessionIDHeader" - $ref: "#/components/parameters/TenantHeader" responses: "200": description: Aggregated envelope with clusters headers: X-Request-ID: $ref: "#/components/headers/XRequestID" X-Cache: $ref: "#/components/headers/XCache" X-Proxy-Mode: $ref: "#/components/headers/XProxyMode" X-Proxy-Tag: $ref: "#/components/headers/XProxyTag" X-Proxy-Used: $ref: "#/components/headers/XProxyUsed" X-Network-Bytes: $ref: "#/components/headers/XNetworkBytes" X-Browser-Profile-Id: $ref: "#/components/headers/XBrowserProfileID" content: application/json: schema: $ref: "#/components/schemas/MegaSearchEnvelope" text/markdown: schema: type: string text/plain: schema: type: string application/x-ndjson: schema: type: string "400": $ref: "#/components/responses/BadRequestError" "403": $ref: "#/components/responses/ForbiddenError" "429": $ref: "#/components/responses/TooManyRequestsError" "502": $ref: "#/components/responses/BadGatewayError" "503": $ref: "#/components/responses/ServiceUnavailableError" "504": $ref: "#/components/responses/GatewayTimeoutError" "500": $ref: "#/components/responses/InternalServerError" /mega/image: get: tags: [Mega] operationId: megaImageSearch summary: Image search across multiple engines with selectable execution mode parameters: - $ref: "#/components/parameters/TextQuery" - $ref: "#/components/parameters/LangQuery" - $ref: "#/components/parameters/RegionQuery" - $ref: "#/components/parameters/DateQuery" - $ref: "#/components/parameters/FileQuery" - $ref: "#/components/parameters/SiteQuery" - $ref: "#/components/parameters/LimitQuery" - $ref: "#/components/parameters/StartQuery" - $ref: "#/components/parameters/FilterQuery" - $ref: "#/components/parameters/AnswersQuery" - $ref: "#/components/parameters/EnginesQuery" - $ref: "#/components/parameters/MegaModeQuery" - $ref: "#/components/parameters/MegaDedupeQuery" - $ref: "#/components/parameters/MegaMergeQuery" - $ref: "#/components/parameters/FormatQuery" - $ref: "#/components/parameters/UseProxyHeader" - $ref: "#/components/parameters/ProxyURLHeader" - $ref: "#/components/parameters/ProxyCountryHeader" - $ref: "#/components/parameters/ProxyClassHeader" - $ref: "#/components/parameters/ProxyProviderHeader" - $ref: "#/components/parameters/ProxySessionIDHeader" - $ref: "#/components/parameters/TenantHeader" responses: "200": description: Aggregated image results envelope headers: X-Request-ID: $ref: "#/components/headers/XRequestID" X-Cache: $ref: "#/components/headers/XCache" X-Proxy-Mode: $ref: "#/components/headers/XProxyMode" X-Proxy-Tag: $ref: "#/components/headers/XProxyTag" X-Proxy-Used: $ref: "#/components/headers/XProxyUsed" X-Network-Bytes: $ref: "#/components/headers/XNetworkBytes" X-Browser-Profile-Id: $ref: "#/components/headers/XBrowserProfileID" content: application/json: schema: $ref: "#/components/schemas/ImageEnvelope" "400": $ref: "#/components/responses/BadRequestError" "403": $ref: "#/components/responses/ForbiddenError" "429": $ref: "#/components/responses/TooManyRequestsError" "502": $ref: "#/components/responses/BadGatewayError" "503": $ref: "#/components/responses/ServiceUnavailableError" "504": $ref: "#/components/responses/GatewayTimeoutError" "500": $ref: "#/components/responses/InternalServerError" /mega/engines: get: tags: [Mega] operationId: listMegaEngines summary: List available engines and runtime state responses: "200": description: Engine list content: application/json: schema: $ref: "#/components/schemas/MegaEnginesResponse" /health: get: tags: [Health] operationId: healthCheck summary: Service health status responses: "200": description: Healthy or degraded service content: application/json: schema: $ref: "#/components/schemas/HealthStatus" "503": description: Unhealthy service content: application/json: schema: $ref: "#/components/schemas/HealthStatus" /ready: get: tags: [Health] operationId: readinessCheck summary: Service readiness status responses: "200": description: Instance is ready content: application/json: schema: $ref: "#/components/schemas/ReadinessStatus" "503": description: Instance is draining content: application/json: schema: $ref: "#/components/schemas/ReadinessStatus" /stats: get: tags: [Stats] operationId: getStats summary: Combined cache, proxy, and circuit-breaker stats responses: "200": description: Runtime statistics content: application/json: schema: $ref: "#/components/schemas/StatsResponse" /stats/cache: get: tags: [Stats] operationId: getCacheStats summary: Cache statistics only responses: "200": description: Cache status content: application/json: schema: $ref: "#/components/schemas/CacheStats" /stats/proxy: get: tags: [Stats] operationId: getProxyStats summary: Proxy pool and per-engine proxy policy statistics responses: "200": description: Proxy stats content: application/json: schema: $ref: "#/components/schemas/ProxyStats" /stats/cb: get: tags: [Stats] operationId: getCircuitBreakerStats summary: Circuit breaker state per engine responses: "200": description: Circuit breaker stats content: application/json: schema: $ref: "#/components/schemas/CircuitBreakerStatsResponse" /openapi.yaml: get: tags: [Docs] operationId: getOpenAPISpec summary: Get raw OpenAPI YAML responses: "200": description: OpenAPI YAML content: application/yaml: schema: type: string /docs: get: tags: [Docs] operationId: getSwaggerUI summary: Swagger UI for interactive API docs responses: "200": description: HTML page loading Swagger UI from CDN content: text/html: schema: type: string components: parameters: EnginePath: name: engine in: path required: true description: Search engine endpoint alias (`duck` is DuckDuckGo). schema: type: string enum: [google, yandex, baidu, bing, duck, ecosia] TextQuery: name: text in: query required: false description: > Search query text. At least one of `text`, `site`, or `file` must be non-empty. schema: type: string example: golang LangQuery: name: lang in: query required: false description: Language code (engine-specific behavior). schema: type: string example: EN RegionQuery: name: region in: query required: false description: > Market/location hint. Yandex accepts numeric `lr` region IDs such as `213`; Google, Bing, and DuckDuckGo use country/locale-style hints such as `RU`, `US`, or `en-GB` where supported. schema: type: string examples: yandex: value: "213" country: value: RU DateQuery: name: date in: query required: false description: Date interval in `YYYYMMDD..YYYYMMDD` format. schema: type: string pattern: "^[0-9]{8}\\.\\.[0-9]{8}$" example: 20250101..20250131 FileQuery: name: file in: query required: false description: File extension filter (for engines that support it). schema: type: string example: PDF SiteQuery: name: site in: query required: false description: Site/domain filter. schema: type: string example: github.com LimitQuery: name: limit in: query required: false description: Maximum organic results to return (1–100). Ads may be returned in addition. schema: type: integer minimum: 1 maximum: 100 default: 25 example: 10 StartQuery: name: start in: query required: false description: Pagination offset (must be >= 0). schema: type: integer minimum: 0 default: 0 example: 20 FilterQuery: name: filter in: query required: false description: Duplicate filtering flag (primarily used by Google parser behavior). schema: type: boolean default: true AnswersQuery: name: answers in: query required: false description: Include answer box style results when supported. schema: type: boolean default: false EnginesQuery: name: engines in: query required: false description: > Comma-separated engine list for mega endpoints. If omitted, all available engines are used. schema: type: string example: google,bing,duckduckgo MegaModeQuery: name: mode in: query required: false description: > Mega execution mode. `balanced` (default) runs all selected engines in parallel. `any` runs selected engines sequentially in request order until first success. `fast` runs only one engine: the fastest by circuit-breaker average response time. schema: type: string enum: [balanced, any, fast] default: balanced MegaDedupeQuery: name: dedupe in: query required: false description: > Enable deduplication by normalized URL. Default `true`. schema: type: boolean default: true MegaMergeQuery: name: merge in: query required: false description: > Merge results from all successful engines into one flat list. Default `true`. When `false`, only the first requested engine that returned results is kept. schema: type: boolean default: true FormatQuery: name: format in: query required: false description: > Output format. `json` (default) returns the envelope. `markdown` returns a Markdown document suitable for Slack/email. `text` returns a minimal plain-text block optimised for LLM context windows. `ndjson` returns one result object per line with no envelope. The `Accept` header is also checked (`text/markdown`, `text/plain`, `application/x-ndjson`). schema: type: string enum: [json, markdown, text, ndjson] default: json UseProxyHeader: name: X-Use-Proxy in: header required: false description: > Request-scoped proxy override. Use `direct` to disable proxy or a tag name to force a specific proxy pool. schema: type: string examples: direct: value: direct tag: value: us ProxyURLHeader: name: X-Proxy-URL in: header required: false description: > Per-request proxy URL supplied by an upstream balancer. Honored only when `proxies.allow_request_proxy_url: true` is set on the worker; otherwise the request is rejected with `400 bad_request` and `reason=REQUEST_PROXY_URL_DISABLED`. Authenticated SOCKS proxies are rejected in browser mode (`reason=UNSUPPORTED_PROXY_SCHEME`). Credentials are never logged or returned. Precedence: `X-Use-Proxy: direct` > `X-Proxy-URL` > `X-Use-Proxy: ` > per-engine configured tag > `proxies.global` > direct. schema: type: string example: http://user:pass@proxy.example:8080 ProxyCountryHeader: name: X-Proxy-Country in: header required: false description: > Two-letter market country code for the supplied proxy. Used as part of the cache key so different markets do not share results. schema: type: string example: us ProxyClassHeader: name: X-Proxy-Class in: header required: false description: Proxy class identifier (e.g. `datacenter`, `residential`, `mobile`). Part of the cache key. schema: type: string example: residential ProxyProviderHeader: name: X-Proxy-Provider in: header required: false description: Upstream proxy provider identifier (e.g. `webshare`, `brightdata`). Part of the cache key. schema: type: string example: webshare ProxySessionIDHeader: name: X-Proxy-Session-ID in: header required: false description: > Sticky session identifier minted by the balancer. Reusing the same value lets OpenSERP reuse cookies and browser profile for that lane. Lanes are LRU-bounded by `proxies.lanes.max_lanes`. Rotating the session ID gives a clean lane. schema: type: string example: sid-123 TenantHeader: name: X-Tenant in: header required: false description: > Optional tenant scope used to namespace sticky lane state across multi-tenant deployments. When present, lanes are keyed by `tenant + engine + session_id`. schema: type: string headers: XRequestID: description: > UUID v7 request identifier. Matches `meta.request_id` in the response body and appears in server logs for correlation. schema: type: string XCache: description: Cache status when cache is enabled (`HIT`, `MISS`, `BYPASS`). schema: type: string enum: [HIT, MISS, BYPASS] XFallbackEngine: description: Engine name used when dedicated endpoint fallback served the response. schema: type: string XProxyMode: description: > Effective proxy mode for the request. `request_url` indicates a per-request `X-Proxy-URL` was honored. schema: type: string enum: [off, tag_pool, request_url] XProxyTag: description: > Effective proxy tag when `X-Proxy-Mode=tag_pool`. Header is omitted when no tag is in effect (i.e. `X-Proxy-Mode` is `request_url` or `off`). schema: type: string XProxyUsed: description: > Effective proxy target used. Values: `direct`, masked `scheme://host:port` URL, `pooled`, `multiple`, or `mixed`. Credentials are never included. schema: type: string XNetworkBytes: description: > Aggregate inbound network bytes consumed while executing the search request. Single-engine endpoints report that engine's request bytes; mega endpoints report the sum across selected engines. Cache hits return `0`. schema: type: integer format: int64 minimum: 0 XBrowserProfileID: description: > Browser profile ID selected for browser-mode execution. Mega endpoints may return a comma-separated list when multiple profiles were used. schema: type: string responses: BadRequestError: description: Invalid request parameters content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: invalidLimit: value: error: bad_request code: 400 message: "INVALID_LIMIT: limit must be between 1 and 100" reason: INVALID_LIMIT emptyQuery: value: error: bad_request code: 400 message: "EMPTY_QUERY: query cannot be empty" reason: EMPTY_QUERY requestProxyURLDisabled: value: error: bad_request code: 400 message: "REQUEST_PROXY_URL_DISABLED: X-Proxy-URL is disabled by server configuration" reason: REQUEST_PROXY_URL_DISABLED unsupportedProxyScheme: value: error: bad_request code: 400 message: "UNSUPPORTED_PROXY_SCHEME: authenticated SOCKS proxies are not supported in browser mode" reason: UNSUPPORTED_PROXY_SCHEME ForbiddenError: description: The search engine blocked the request content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: blocked: value: error: blocked code: 403 message: "search engine blocked the request" meta: engine: google proxy_used: http://proxy.example:8080 proxy_country: us proxy_class: residential proxy_provider: webshare proxy_session_id: sid-123 TooManyRequestsError: description: Captcha challenge or rate-limit response from the search engine content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: captcha: value: error: captcha_detected code: 429 message: "captcha detected" meta: engine: google proxy_used: http://proxy.example:8080 proxy_session_id: sid-123 rateLimited: value: error: rate_limited code: 429 message: "search engine rate limited the request" meta: engine: google proxy_used: http://proxy.example:8080 BadGatewayError: description: Engine internal failure, parser drift, or all-engine failure when fallback is enabled. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: parserFailure: value: error: parser_failure code: 502 message: "parser failure" meta: engine: google engineInternal: value: error: engine_internal code: 502 message: "engine internal error" meta: engine: google allEnginesFailed: value: error: engine_internal code: 502 message: "all search engines failed" meta: engine: google GatewayTimeoutError: description: Search timed out waiting for required SERP elements content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: searchTimeout: value: error: search_timeout code: 504 message: "timeout. Cannot find element on page" meta: engine: google ServiceUnavailableError: description: Proxy-layer failure (no healthy proxy or transport error). The search itself was not produced. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: proxyConnect: value: error: proxy_connect code: 503 message: "proxy connection failed" meta: engine: google proxy_used: http://proxy.example:8080 proxy_country: us proxyAuth: value: error: proxy_auth code: 503 message: "proxy authentication failed" meta: engine: google proxy_used: http://proxy.example:8080 proxyTimeout: value: error: proxy_timeout code: 503 message: "proxy request timed out" meta: engine: google proxy_used: http://proxy.example:8080 proxyUnavailable: value: error: proxy_unavailable code: 503 message: "proxy unavailable" meta: engine: google NotFoundError: description: Endpoint not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" InternalServerError: description: Internal error while handling request content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" schemas: # ── v2 envelope ────────────────────────────────────────────────── QueryEcho: type: object required: [text, engines_requested] properties: text: type: string example: golang lang: type: string example: EN region: type: string example: US engines_requested: type: array items: type: string example: [google] ResponseMeta: type: object required: [request_id, requested_at, took_ms, engines_failed, version] properties: request_id: type: string example: 01HXYZ... requested_at: type: string format: date-time example: "2026-04-24T12:00:00Z" took_ms: type: integer example: 842 engines_failed: type: array items: type: string example: [] engine_errors: type: array description: Sanitized per-engine failures for mega endpoints. items: $ref: "#/components/schemas/EngineErrorDetail" version: type: string example: "2.1" EngineErrorDetail: type: object required: [engine, error] properties: engine: type: string example: bing error: type: string example: blocked message: type: string description: Sanitized detail; proxy credentials are never included. example: "blocked: 403" Pagination: type: object required: [page, has_more, next_start] properties: page: type: integer minimum: 1 example: 1 has_more: type: boolean example: true next_start: type: integer example: 25 # ── Result ─────────────────────────────────────────────────────── Position: type: object required: [absolute] properties: absolute: type: integer description: > 1-based rank in the mixed SERP stream, across both organic and ad blocks. Always present so SEO callers can plot rank vs. on-page position without inferring it from result order. example: 2 DomainInfo: type: object required: [category] properties: tld: type: string example: org sld: type: string example: wikipedia category: type: string description: Empty string when the domain matches no known category. enum: ["", gov, edu, mil, news, forum, marketplace, social] Classification: type: object properties: content_type: type: string enum: [article, document, video, forum_thread, webpage] example: article source_hint: type: string example: encyclopedia ResultType: type: string description: > SERP block type. New values require a minor version bump (`meta.version: "2.1"`). In v2.1, engines may emit specialized modules such as `people_also_ask` when a parser can classify them reliably. enum: - organic - ad - featured_snippet - knowledge_panel - people_also_ask - video - image - news - shopping - local - answer_box Result: type: object required: - id - rank - type - title - url - display_url - snippet - domain - favicon - position - engine properties: id: type: string description: > Stable identifier: `s_` + hex(first 8 bytes of MD5(engine|normalized_url)). Normalized URL: lowercase scheme+host, trailing slash stripped, utm_*/fbclid/gclid tracking params removed. Same URL → same ID across requests. example: s_a1b2c3d4e5f6a1b2 rank: type: integer example: 1 type: $ref: "#/components/schemas/ResultType" title: type: string example: The Go Programming Language url: type: string example: https://go.dev/ display_url: type: string description: Breadcrumb form of the URL (e.g. `go.dev › doc › install`). example: go.dev snippet: type: string example: Go is an open source programming language... domain: type: string description: Registrable domain with leading `www.` stripped. example: go.dev favicon: type: string description: Constructed as `https://{domain}/favicon.ico`. Not probed. example: https://go.dev/favicon.ico position: $ref: "#/components/schemas/Position" engine: type: string example: google domain_info: $ref: "#/components/schemas/DomainInfo" classification: $ref: "#/components/schemas/Classification" # ── Image result ───────────────────────────────────────────────── ImageData: type: object required: [url] properties: url: type: string example: https://example.com/gopher.png thumbnail: type: string example: https://example.com/gopher-thumb.png width: type: integer example: 1200 height: type: integer example: 800 ImageSource: type: object required: [page_url, domain] properties: page_url: type: string example: https://example.com/article-about-gophers domain: type: string example: example.com ImageResult: type: object required: [id, rank, type, title, image, source, engine] properties: id: type: string description: Stable identifier prefixed with `i_`. example: i_a1b2c3d4e5f6a1b2 rank: type: integer example: 1 type: type: string enum: [image] title: type: string image: $ref: "#/components/schemas/ImageData" source: $ref: "#/components/schemas/ImageSource" engine: type: string # ── Clusters (mega only) ────────────────────────────────────────── ClusterOccurrence: type: object required: [engine, rank, result_id] properties: engine: type: string example: google rank: type: integer example: 1 result_id: type: string example: s_a1b2c3d4e5f6a1b2 Cluster: type: object required: [id, canonical_url, domain, title, occurrences, engines_count, best_rank, score] properties: id: type: string description: > Stable identifier: `c_` + hex(first 8 bytes of MD5(normalized_url)). example: c_a1b2c3d4e5f6a1b2 canonical_url: type: string example: https://go.dev/ domain: type: string example: go.dev title: type: string example: The Go Programming Language occurrences: type: array items: $ref: "#/components/schemas/ClusterOccurrence" engines_count: type: integer description: Number of engines this URL appeared in. example: 3 best_rank: type: integer description: Lowest (best) rank seen across all engines. example: 1 score: type: number format: float description: > Cross-engine agreement score: sum(1/rank for each occurrence) / engines_queried, capped at 1.0. Higher is better. example: 0.92 # ── Envelopes ───────────────────────────────────────────────────── SearchEnvelope: type: object required: [query, meta, results, pagination] properties: query: $ref: "#/components/schemas/QueryEcho" meta: $ref: "#/components/schemas/ResponseMeta" results: type: array items: $ref: "#/components/schemas/Result" pagination: $ref: "#/components/schemas/Pagination" MegaSearchEnvelope: allOf: - $ref: "#/components/schemas/SearchEnvelope" - type: object properties: clusters: type: array nullable: true items: $ref: "#/components/schemas/Cluster" description: > Cross-engine clusters, sorted by score descending. Only present on /mega/search responses. Absent (not null) on single-engine endpoints. ImageEnvelope: type: object required: [query, meta, results, pagination] properties: query: $ref: "#/components/schemas/QueryEcho" meta: $ref: "#/components/schemas/ResponseMeta" results: type: array items: $ref: "#/components/schemas/ImageResult" pagination: $ref: "#/components/schemas/Pagination" # ── Error ───────────────────────────────────────────────────────── ErrorResponse: type: object required: [error, code] properties: error: type: string description: > Stable machine-readable error class. Search-pipeline failures use the following codes: `captcha_detected`, `blocked`, `rate_limited`, `search_timeout`, `proxy_connect`, `proxy_auth`, `proxy_timeout`, `proxy_unavailable`, `parser_failure`, `engine_internal`, `all_engines_failed`, `circuit_open`, `request_timeout`, `request_canceled`. Validation errors use `bad_request`. Other generic codes (`not_found`, `service_unavailable`, `server_error`, `client_error`, `error`) may appear for non-search routes. enum: - bad_request - not_found - rate_limited - service_unavailable - server_error - client_error - error - captcha_detected - blocked - search_timeout - proxy_connect - proxy_auth - proxy_timeout - proxy_unavailable - parser_failure - engine_internal - all_engines_failed - circuit_open - request_timeout - request_canceled example: bad_request code: type: integer example: 400 request_id: type: string description: Matches the `X-Request-ID` response header. example: 01HXYZ... message: type: string example: "INVALID_LIMIT: limit must be between 1 and 100" reason: type: string description: > Stable client-actionable reason code. Present on 400 errors. Known values: INVALID_LIMIT, INVALID_START, INVALID_PARAM, EMPTY_QUERY, NO_ENGINES, UNKNOWN_FORMAT, REQUEST_PROXY_URL_DISABLED, UNSUPPORTED_PROXY_SCHEME. example: INVALID_LIMIT meta: type: object description: > Sanitized context for search-pipeline errors. Credentials are never included. additionalProperties: true properties: engine: type: string example: google proxy_used: type: string description: Masked `scheme://host:port`; never includes credentials. example: http://proxy.example:8080 proxy_country: type: string example: us proxy_class: type: string example: residential proxy_provider: type: string example: webshare proxy_session_id: type: string example: sid-123 error_detail: type: string description: Sanitized underlying error detail when available. example: "proxy_connect: dial tcp proxy.example:8080: connection refused" engine_errors: type: array items: $ref: "#/components/schemas/EngineErrorDetail" # ── Health / Stats ──────────────────────────────────────────────── EngineHealth: type: object required: [name, initialized, status] properties: name: type: string example: google initialized: type: boolean example: true status: type: string enum: [ready, not_initialized, circuit_open] HealthStatus: type: object required: [status, uptime, engines, system] properties: status: type: string enum: [healthy, degraded, unhealthy] uptime: type: string example: 1h12m3s engines: type: array items: $ref: "#/components/schemas/EngineHealth" system: type: object additionalProperties: true ReadinessStatus: type: object required: [status] properties: status: type: string enum: [ready, draining] CacheStatsEnabled: type: object required: [status, entries, hits, misses, bypasses, evictions, ttl_seconds, max_size] properties: status: type: boolean enum: [true] entries: type: integer hits: type: integer misses: type: integer bypasses: type: integer evictions: type: integer ttl_seconds: type: integer max_size: type: integer CacheStatsDisabled: type: object required: [status] properties: status: type: boolean enum: [false] CacheStats: oneOf: - $ref: "#/components/schemas/CacheStatsEnabled" - $ref: "#/components/schemas/CacheStatsDisabled" ProxyTagSummary: type: object required: [configured, healthy] properties: configured: type: integer healthy: type: integer ProxyStatsEntry: type: object required: [proxy, tags, healthy, failures, disabled] properties: proxy: type: string tags: type: array items: type: string healthy: type: boolean failures: type: integer disabled: type: boolean ProxyEngineStats: type: object required: [selected_proxy] properties: tag: type: string selected_proxy: type: string LaneStats: type: object required: [active, evicted_lru, cookies_dropped] description: Sticky proxy lane state observed by this worker. properties: active: type: integer description: Number of lanes currently held by the worker. example: 12 evicted_lru: type: integer description: Lanes evicted by the LRU bound since worker start. example: 7 cookies_dropped: type: integer description: Lane cookie drops triggered by captcha/challenge responses. example: 20 BrowserPoolStats: type: object required: [active, max, evicted_lru, evicted_idle] description: > Live state of the per-process Chrome pool. Each authenticated upstream proxy identity (scheme+host+port+username) gets a dedicated Chrome so Chrome can answer 407 challenges natively. Direct and unauthenticated proxies share one Chrome with per-BrowserContext proxy override. properties: active: type: integer description: Number of Chrome processes currently held by the pool. example: 3 max: type: integer description: Configured `app.max_processes` LRU cap. example: 4 evicted_lru: type: integer description: Chrome processes closed because the LRU cap was exceeded. example: 12 evicted_idle: type: integer description: Chrome processes closed by the idle sweeper after `app.idle_ttl`. example: 5 ProxyStats: type: object required: [ configured_count, healthy_count, unhealthy_count, request_proxy_url_enabled, lanes, browser_processes, tags, entries, ] properties: configured_count: type: integer healthy_count: type: integer unhealthy_count: type: integer request_proxy_url_enabled: type: boolean description: Whether `proxies.allow_request_proxy_url` is enabled on this worker. lanes: $ref: "#/components/schemas/LaneStats" browser_processes: $ref: "#/components/schemas/BrowserPoolStats" tags: type: object additionalProperties: $ref: "#/components/schemas/ProxyTagSummary" entries: type: array items: $ref: "#/components/schemas/ProxyStatsEntry" engines: type: object additionalProperties: $ref: "#/components/schemas/ProxyEngineStats" CircuitBreakerStat: type: object required: [engine, state, failure_count, last_changed] properties: engine: type: string state: type: string enum: [closed, open, half-open] failure_count: type: integer last_changed: type: string format: date-time retry_in: type: integer description: Seconds until next half-open attempt (present when state is open). avg_response_ms: type: integer description: Average successful engine response time in milliseconds. CircuitBreakerStatsResponse: type: object required: [circuit_breakers] properties: circuit_breakers: type: array items: $ref: "#/components/schemas/CircuitBreakerStat" StatsResponse: type: object required: [cache, proxy, circuit_breakers] properties: cache: $ref: "#/components/schemas/CacheStats" proxy: $ref: "#/components/schemas/ProxyStats" circuit_breakers: type: array items: $ref: "#/components/schemas/CircuitBreakerStat" MegaEngineInfo: type: object required: [name, initialized] properties: name: type: string initialized: type: boolean circuit_state: type: string enum: [closed, open, half-open] MegaEnginesResponse: type: object required: [engines, total] properties: engines: type: array items: $ref: "#/components/schemas/MegaEngineInfo" total: type: integer