{
	"openapi": "3.1.0",
	"info": {
		"title": "Proxlane",
		"version": "1",
		"summary": "One endpoint in front of every scraping API.",
		"description": "Routes a scrape across ScraperAPI, ScrapingBee, Scrapfly and Bright Data. When a provider blocks, errors or times out the next one is tried, and the response headers say what happened at every step.\n\nThis file is generated from the gateway's own outcome taxonomy, so the status codes and the X-Outcome enum below are the ones the router actually uses.\n\nSelf-hosted: there is no shared base URL. Point `servers` at your own gateway.\n\n## What is safe to depend on\n\n`X-Outcome-Class` is a CLOSED set. It has six members and will never gain a seventh, so a switch on it cannot be broken by a release. Branch on it.\n\n`X-Outcome` is OPEN and gains members as new failure modes are told apart. Read it for detail, log it, show it to a human — but do not exhaustively match on it.\n\nThe URL surface is versioned in the path (`/v1`). While the gateway is 0.x a breaking change to that surface arrives as a MINOR release, not a major, because semver reserves nothing below 1.0. Pin the image tag rather than tracking `latest` if that matters to you; `docker/compose.yml` in the repo ships pinned for the same reason.",
		"license": {
			"name": "AGPL-3.0-only",
			"identifier": "AGPL-3.0-only"
		}
	},
	"externalDocs": {
		"url": "https://proxlane.dev/docs",
		"description": "Documentation"
	},
	"servers": [
		{
			"url": "{gateway}",
			"description": "Your own deployment. The gateway is self-hosted; we run no shared one.",
			"variables": {
				"gateway": {
					"default": "http://localhost:8787"
				}
			}
		}
	],
	"security": [
		{
			"bearerAuth": []
		},
		{
			"apiKeyQuery": []
		}
	],
	"paths": {
		"/v1": {
			"get": {
				"operationId": "scrape",
				"summary": "Scrape a URL",
				"description": "Fetches the target through the first capable provider, failing over on a block, a provider error or a timeout.",
				"parameters": [
					{
						"name": "url",
						"required": true,
						"schema": {
							"type": "string",
							"format": "uri"
						},
						"description": "The page to fetch. Rejected at the edge with TARGET_FORBIDDEN if it resolves to a private range, a denylisted host, or a cloud metadata address.",
						"example": "https://example.com",
						"in": "query"
					},
					{
						"name": "api_key",
						"required": false,
						"schema": {
							"type": "string"
						},
						"description": "The gateway's key, not a provider's. Accepted because it is what the providers this replaces accept, which is what makes migration a hostname change. Prefer the Authorization header: query strings reach access logs, proxy logs, Referer headers and error trackers.",
						"in": "query"
					},
					{
						"name": "render",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"true",
								"1"
							]
						},
						"description": "Run JavaScript on the target. Only \"true\" and \"1\" enable it; every other value, including omitting the parameter, means false. Presence alone is never truth, or render=false would render and cost about five times as much.",
						"in": "query"
					},
					{
						"name": "premium",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"none",
								"residential",
								"stealth"
							],
							"default": "none"
						},
						"description": "Proxy tier.",
						"in": "query"
					},
					{
						"name": "country_code",
						"required": false,
						"schema": {
							"type": "string",
							"pattern": "^[A-Za-z]{2}$"
						},
						"description": "ISO 3166-1 alpha-2. Where the request should appear to come from, subject to provider coverage.",
						"in": "query"
					},
					{
						"name": "provider",
						"required": false,
						"schema": {
							"type": "string"
						},
						"description": "Pin one provider and disable failover. A benchmarking escape hatch. If that provider cannot serve the request you get NO_PROVIDER_AVAILABLE rather than a silent substitution.",
						"in": "query"
					},
					{
						"name": "binary",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"true",
								"1"
							]
						},
						"description": "Ask for the response body byte for byte — an image, a PDF, anything not text. Narrows the chain to providers that can deliver it, which is not all of them: measured on 2026-08-19, two of the four launch providers destroy binary, one by decoding it as UTF-8 and one by wrapping it in a JSON envelope. Without this a request for an image returns 200 with a quietly corrupted body. If no configured provider can serve bytes the answer is NO_PROVIDER_AVAILABLE, which is the honest failure.",
						"in": "query"
					},
					{
						"name": "wait_for",
						"required": false,
						"schema": {
							"type": "string",
							"maxLength": 256
						},
						"description": "A CSS selector the renderer must see before it snapshots the page. Implies render=true: a wait condition with no renderer to wait is not a request anyone means, so it is honoured rather than rejected. Rendering means the renderer ran, not that the content arrived — on a late-hydrating page the same request returns the full listing on one attempt and an empty shell on the next, and this is how you name the finish line. Narrows the chain to providers that can express it, which is not all of them: measured on 2026-08-29, Bright Data accepts the directive and could not be shown to enforce it, so it declares it cannot and is filtered out rather than charging you for a page that did not wait. Refused as BAD_REQUEST if empty, longer than 256 characters, or carrying a control character.",
						"in": "query"
					},
					{
						"name": "timeout",
						"required": false,
						"schema": {
							"type": "integer",
							"minimum": 8000
						},
						"description": "Deadline for the whole request in milliseconds, every failover hop included. Defaults to the server's PROXLANE_DEADLINE_MS and is capped at it: a caller may ask for less time than the operator budgeted, never more. Below 8000 a single attempt cannot finish, so it is rejected as BAD_REQUEST rather than timing out having tried nothing.",
						"in": "query"
					}
				],
				"responses": {
					"200": {
						"description": "The target's own response, passed through unchanged. That is the drop-in promise: code that already branches on a 404 keeps working, so a target's 404 arrives as a 404 and not as an error of ours.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"*/*": {
								"schema": {
									"type": "string"
								}
							}
						}
					},
					"400": {
						"description": "BAD_REQUEST: The client's request is malformed or impossible.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"401": {
						"description": "The gateway key was missing or wrong. Deliberately NOT an outcome: outcomes describe what happened to a scrape, and a request rejected at the door never became one. Reusing AUTH_FAILED here would put gateway auth failures into provider health.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"403": {
						"description": "TARGET_FORBIDDEN: Target rejected at our edge: private range, denylist, metadata address.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"404": {
						"description": "TARGET_NOT_FOUND: Genuine 404, unless the provider has retry_404 semantics.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"413": {
						"description": "RESPONSE_TOO_LARGE: Body exceeded the cap; see operations.md section 1.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"429": {
						"description": "TARGET_RATE_LIMITED: Target rate-limited us (429); backs off per domain rather than retrying. RATE_LIMITED: Provider 429 or concurrency cap. GATEWAY_BUSY: In-flight ceiling reached; the gateway shed this request rather than queue it.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"500": {
						"description": "INVALID_REQUEST: Our translation produced a provider 400.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"502": {
						"description": "SOFT_BLOCK: our detector fired on the body; a rule ID is attached. HARD_BLOCK: Provider says blocked or banned. TARGET_ERROR: Target site 5xx or DNS dead. PROVIDER_ERROR: Provider 5xx or infrastructure failure. AUTH_FAILED: Provider 401/403 on the key. PROVIDER_DRIFT: Response failed its Zod schema. PROVIDER_BODY_OFFLOADED: the provider stored the body out of band and returned a pointer we cannot follow.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"503": {
						"description": "NO_PROVIDER_AVAILABLE: No adapter matches the capability, or the chain is exhausted.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"504": {
						"description": "PROVIDER_TIMEOUT: Attempt exceeded its per-attempt budget. BUDGET_EXCEEDED: Global deadline or cost budget hit.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					}
				}
			},
			"post": {
				"operationId": "scrapePost",
				"summary": "Scrape a URL, forwarding a request body",
				"description": "As GET, and the body is forwarded to the target as text, byte for byte. It is not parsed: guessing between JSON and form encoding would corrupt one of them. The response body cap applies to the request body too.",
				"parameters": [
					{
						"name": "url",
						"required": true,
						"schema": {
							"type": "string",
							"format": "uri"
						},
						"description": "The page to fetch. Rejected at the edge with TARGET_FORBIDDEN if it resolves to a private range, a denylisted host, or a cloud metadata address.",
						"example": "https://example.com",
						"in": "query"
					},
					{
						"name": "api_key",
						"required": false,
						"schema": {
							"type": "string"
						},
						"description": "The gateway's key, not a provider's. Accepted because it is what the providers this replaces accept, which is what makes migration a hostname change. Prefer the Authorization header: query strings reach access logs, proxy logs, Referer headers and error trackers.",
						"in": "query"
					},
					{
						"name": "render",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"true",
								"1"
							]
						},
						"description": "Run JavaScript on the target. Only \"true\" and \"1\" enable it; every other value, including omitting the parameter, means false. Presence alone is never truth, or render=false would render and cost about five times as much.",
						"in": "query"
					},
					{
						"name": "premium",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"none",
								"residential",
								"stealth"
							],
							"default": "none"
						},
						"description": "Proxy tier.",
						"in": "query"
					},
					{
						"name": "country_code",
						"required": false,
						"schema": {
							"type": "string",
							"pattern": "^[A-Za-z]{2}$"
						},
						"description": "ISO 3166-1 alpha-2. Where the request should appear to come from, subject to provider coverage.",
						"in": "query"
					},
					{
						"name": "provider",
						"required": false,
						"schema": {
							"type": "string"
						},
						"description": "Pin one provider and disable failover. A benchmarking escape hatch. If that provider cannot serve the request you get NO_PROVIDER_AVAILABLE rather than a silent substitution.",
						"in": "query"
					},
					{
						"name": "binary",
						"required": false,
						"schema": {
							"type": "string",
							"enum": [
								"true",
								"1"
							]
						},
						"description": "Ask for the response body byte for byte — an image, a PDF, anything not text. Narrows the chain to providers that can deliver it, which is not all of them: measured on 2026-08-19, two of the four launch providers destroy binary, one by decoding it as UTF-8 and one by wrapping it in a JSON envelope. Without this a request for an image returns 200 with a quietly corrupted body. If no configured provider can serve bytes the answer is NO_PROVIDER_AVAILABLE, which is the honest failure.",
						"in": "query"
					},
					{
						"name": "wait_for",
						"required": false,
						"schema": {
							"type": "string",
							"maxLength": 256
						},
						"description": "A CSS selector the renderer must see before it snapshots the page. Implies render=true: a wait condition with no renderer to wait is not a request anyone means, so it is honoured rather than rejected. Rendering means the renderer ran, not that the content arrived — on a late-hydrating page the same request returns the full listing on one attempt and an empty shell on the next, and this is how you name the finish line. Narrows the chain to providers that can express it, which is not all of them: measured on 2026-08-29, Bright Data accepts the directive and could not be shown to enforce it, so it declares it cannot and is filtered out rather than charging you for a page that did not wait. Refused as BAD_REQUEST if empty, longer than 256 characters, or carrying a control character.",
						"in": "query"
					},
					{
						"name": "timeout",
						"required": false,
						"schema": {
							"type": "integer",
							"minimum": 8000
						},
						"description": "Deadline for the whole request in milliseconds, every failover hop included. Defaults to the server's PROXLANE_DEADLINE_MS and is capped at it: a caller may ask for less time than the operator budgeted, never more. Below 8000 a single attempt cannot finish, so it is rejected as BAD_REQUEST rather than timing out having tried nothing.",
						"in": "query"
					}
				],
				"requestBody": {
					"required": false,
					"content": {
						"*/*": {
							"schema": {
								"type": "string"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The target's own response, passed through unchanged. That is the drop-in promise: code that already branches on a 404 keeps working, so a target's 404 arrives as a 404 and not as an error of ours.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"*/*": {
								"schema": {
									"type": "string"
								}
							}
						}
					},
					"400": {
						"description": "BAD_REQUEST: The client's request is malformed or impossible.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"401": {
						"description": "The gateway key was missing or wrong. Deliberately NOT an outcome: outcomes describe what happened to a scrape, and a request rejected at the door never became one. Reusing AUTH_FAILED here would put gateway auth failures into provider health.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"403": {
						"description": "TARGET_FORBIDDEN: Target rejected at our edge: private range, denylist, metadata address.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"404": {
						"description": "TARGET_NOT_FOUND: Genuine 404, unless the provider has retry_404 semantics.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"413": {
						"description": "RESPONSE_TOO_LARGE: Body exceeded the cap; see operations.md section 1.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"429": {
						"description": "TARGET_RATE_LIMITED: Target rate-limited us (429); backs off per domain rather than retrying. RATE_LIMITED: Provider 429 or concurrency cap. GATEWAY_BUSY: In-flight ceiling reached; the gateway shed this request rather than queue it.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"500": {
						"description": "INVALID_REQUEST: Our translation produced a provider 400.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"502": {
						"description": "SOFT_BLOCK: our detector fired on the body; a rule ID is attached. HARD_BLOCK: Provider says blocked or banned. TARGET_ERROR: Target site 5xx or DNS dead. PROVIDER_ERROR: Provider 5xx or infrastructure failure. AUTH_FAILED: Provider 401/403 on the key. PROVIDER_DRIFT: Response failed its Zod schema. PROVIDER_BODY_OFFLOADED: the provider stored the body out of band and returned a pointer we cannot follow.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"503": {
						"description": "NO_PROVIDER_AVAILABLE: No adapter matches the capability, or the chain is exhausted.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					},
					"504": {
						"description": "PROVIDER_TIMEOUT: Attempt exceeded its per-attempt budget. BUDGET_EXCEEDED: Global deadline or cost budget hit.",
						"headers": {
							"X-Outcome": {
								"$ref": "#/components/headers/X-Outcome"
							},
							"X-Cost-Unit": {
								"$ref": "#/components/headers/X-Cost-Unit"
							},
							"X-Cost-Source": {
								"$ref": "#/components/headers/X-Cost-Source"
							},
							"X-Outcome-Class": {
								"$ref": "#/components/headers/X-Outcome-Class"
							},
							"X-Attempts": {
								"$ref": "#/components/headers/X-Attempts"
							},
							"X-Chain": {
								"$ref": "#/components/headers/X-Chain"
							},
							"X-Cost-Estimate": {
								"$ref": "#/components/headers/X-Cost-Estimate"
							},
							"Server-Timing": {
								"$ref": "#/components/headers/Server-Timing"
							},
							"X-Request-Id": {
								"$ref": "#/components/headers/X-Request-Id"
							},
							"X-Provider-Used": {
								"$ref": "#/components/headers/X-Provider-Used"
							},
							"X-Ignored-Params": {
								"$ref": "#/components/headers/X-Ignored-Params"
							},
							"X-Detect-Rule": {
								"$ref": "#/components/headers/X-Detect-Rule"
							},
							"X-Provider-Health": {
								"$ref": "#/components/headers/X-Provider-Health"
							},
							"Retry-After": {
								"$ref": "#/components/headers/Retry-After"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					}
				}
			}
		},
		"/health": {
			"get": {
				"operationId": "health",
				"summary": "Liveness",
				"description": "Takes no key. Reports that the process is up and serving, not that it is fully configured: a gateway with no provider keys is correctly running and will answer NO_PROVIDER_AVAILABLE. Returns the provider COUNT, never the names, because which providers an operator pays for is not something to hand out. Never shed under load. `version` is the running build, so a deploy can be verified: publishing an image is not deploying it, and an orchestrator keeps serving the digest it started with until something issues an update.",
				"security": [],
				"responses": {
					"200": {
						"description": "The process is serving.",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"required": [
										"status",
										"version",
										"providers"
									],
									"properties": {
										"status": {
											"type": "string",
											"enum": [
												"ok"
											]
										},
										"version": {
											"type": "string",
											"example": "0.4.0"
										},
										"providers": {
											"type": "integer",
											"minimum": 0
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/health/providers": {
			"get": {
				"operationId": "providerHealth",
				"summary": "Per-provider health state",
				"description": "Only meaningful with PROXLANE_HEALTH=on, which is off by default because the statistic assumes independent failures and real providers have bad hours instead.",
				"responses": {
					"200": {
						"description": "Health state per provider.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ProviderHealthBody"
								}
							}
						}
					},
					"401": {
						"description": "The gateway key was missing or wrong.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					}
				}
			}
		},
		"/health/cooldowns": {
			"get": {
				"operationId": "cooldowns",
				"summary": "What is cooling, and what recently expired",
				"description": "Expired entries are included deliberately: the consecutive count is what makes the next cooldown on that key longer.",
				"responses": {
					"200": {
						"description": "Cooling and recently expired entries.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/CooldownsBody"
								}
							}
						}
					},
					"401": {
						"description": "The gateway key was missing or wrong.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorBody"
								}
							}
						}
					}
				}
			}
		}
	},
	"components": {
		"securitySchemes": {
			"bearerAuth": {
				"type": "http",
				"scheme": "bearer",
				"description": "The gateway's key. Preferred over the query parameter."
			},
			"apiKeyQuery": {
				"type": "apiKey",
				"in": "query",
				"name": "api_key",
				"description": "The drop-in migration surface. Works, but a query string is the worst place to put a credential."
			}
		},
		"headers": {
			"X-Outcome": {
				"description": "What happened. Open: it gains members as adapters land, so branch on X-Outcome-Class instead. Absent when the request never became a scrape — a 401 or a 501 has no outcome to report, though it still carries X-Outcome-Class.",
				"schema": {
					"$ref": "#/components/schemas/Outcome"
				}
			},
			"X-Cost-Unit": {
				"description": "What X-Cost-Estimate counts. `provider-credits` is the serving provider's own credits; `usd-cents` is money, used by providers that bill per request and issue no credits. Absent when a failover chain spent in more than one unit, in which case X-Cost-Estimate is the literal string `mixed` rather than a sum of incomparable numbers — the per-attempt figures in the body each carry their own unit.",
				"schema": {
					"type": "string",
					"enum": [
						"provider-credits",
						"usd-cents"
					]
				}
			},
			"X-Cost-Source": {
				"description": "Whose number X-Cost-Estimate is. `reported` means the providers told us what they charged and the adapter passed it through — three of the four do. `estimated` means they said nothing and we applied our own table, which is our model of their pricing rather than their answer, and is the one worth treating with suspicion. `mixed` when a chain used both, which a failover across providers routinely does. Absent when nothing was charged.",
				"schema": {
					"type": "string",
					"enum": [
						"reported",
						"estimated",
						"mixed"
					]
				}
			},
			"X-Outcome-Class": {
				"description": "The coarse class. Closed: these six never grow. This is the one to branch on.",
				"schema": {
					"$ref": "#/components/schemas/OutcomeClass"
				}
			},
			"X-Attempts": {
				"description": "How many providers were tried.",
				"schema": {
					"type": "integer",
					"minimum": 0
				}
			},
			"X-Chain": {
				"description": "Every attempt as `provider:outcome`, in order. The only header that names who FAILED — X-Provider-Used names the winner, so a request that failed over and then succeeded otherwise looks like a clean single-hop 200. A single-attempt request has a one-element chain; the header is omitted, never empty, when no provider was tried at all.",
				"schema": {
					"type": "string",
					"example": "scraperapi:PROVIDER_TIMEOUT>scrapfly:OK"
				}
			},
			"X-Cost-Estimate": {
				"description": "Credits, summed across every attempt including the ones that failed. A failover that burned two charged hops reports both.",
				"schema": {
					"type": "string"
				}
			},
			"Server-Timing": {
				"description": "gw is gateway-internal time, up is time inside provider calls, total is both. Split by subtraction, so anything unaccounted for lands in gw where it is visible.",
				"schema": {
					"type": "string"
				}
			},
			"X-Request-Id": {
				"description": "Present on every response, including 401s. Echoes the caller's X-Request-Id when it is usable. Quote it in a support thread.",
				"schema": {
					"type": "string"
				}
			},
			"X-Provider-Used": {
				"description": "The provider that served. Absent, never empty, when nothing served.",
				"schema": {
					"type": "string"
				}
			},
			"X-Ignored-Params": {
				"description": "Query parameters this request sent that the gateway does not read, comma-separated and sorted. Absent when there were none. An unknown parameter is not an error — ScraperAPI accepts parameters we do not implement, and rejecting them would break the hostname-change migration on day one — but it is not silent either: js_render and js are other providers’ spellings of render, and being ignored means an unrendered page at a fifth of the cost, which looks exactly like success.",
				"schema": {
					"type": "string"
				}
			},
			"X-Detect-Rule": {
				"description": "The block-page rule that fired. Present only when one did: emitting \"none\" everywhere would assert the detector ran and found nothing, which is untrue for a request that never reached a provider.",
				"schema": {
					"type": "string"
				}
			},
			"X-Provider-Health": {
				"description": "Present when health tracking is on, or when a routing floor fired. demoted-forced means every capable provider was demoted and the least bad was used anyway. cooling-forced means every capable provider was on cooldown and one was tried regardless, rate-limited per domain, rather than take the domain off the air for the length of the backoff.",
				"schema": {
					"type": "string"
				}
			},
			"Retry-After": {
				"description": "Seconds, rounded up. Sent only when the gateway knows: a guessed value is worse than none, because a caller will believe it.",
				"schema": {
					"type": "integer",
					"minimum": 1
				}
			}
		},
		"schemas": {
			"Outcome": {
				"type": "string",
				"description": "Open. Gains members as adapters land.",
				"enum": [
					"OK",
					"SOFT_BLOCK",
					"HARD_BLOCK",
					"TARGET_NOT_FOUND",
					"TARGET_ERROR",
					"TARGET_RATE_LIMITED",
					"PROVIDER_TIMEOUT",
					"PROVIDER_ERROR",
					"RATE_LIMITED",
					"AUTH_FAILED",
					"PROVIDER_DRIFT",
					"PROVIDER_BODY_OFFLOADED",
					"INVALID_REQUEST",
					"BAD_REQUEST",
					"TARGET_FORBIDDEN",
					"NO_PROVIDER_AVAILABLE",
					"RESPONSE_TOO_LARGE",
					"BUDGET_EXCEEDED",
					"GATEWAY_BUSY"
				]
			},
			"OutcomeClass": {
				"type": "string",
				"description": "Closed. These six never grow, which is why callers should branch here.",
				"enum": [
					"ok",
					"blocked",
					"target",
					"provider",
					"client",
					"gateway"
				]
			},
			"ProviderHealthBody": {
				"type": "object",
				"required": [
					"providers"
				],
				"properties": {
					"providers": {
						"type": "array",
						"items": {
							"type": "object",
							"required": [
								"id",
								"state",
								"baselineFailureRate",
								"statistic",
								"samplesInState"
							],
							"properties": {
								"id": {
									"type": "string",
									"description": "Adapter id, e.g. `scraperapi`."
								},
								"state": {
									"type": "string",
									"enum": [
										"healthy",
										"degraded",
										"demoted"
									],
									"description": "`degraded` moves the provider down the chain; `demoted` removes it unless it is the last capable one."
								},
								"baselineFailureRate": {
									"type": [
										"number",
										"null"
									],
									"description": "The frozen p0 the statistic is measured against. Null until enough samples exist to bootstrap it."
								},
								"statistic": {
									"type": "number",
									"description": "The accumulated CUSUM value. Crosses a threshold to change state."
								},
								"samplesInState": {
									"type": "integer"
								}
							}
						}
					}
				}
			},
			"CooldownEntry": {
				"type": "object",
				"required": [
					"scope",
					"provider",
					"expiresInMs",
					"consecutive",
					"probeTaken"
				],
				"properties": {
					"scope": {
						"type": "string",
						"enum": [
							"domain",
							"account"
						],
						"description": "`domain` is a block fact and is shared; `account` is one org's key and is not."
					},
					"provider": {
						"type": "string"
					},
					"domain": {
						"type": "string",
						"description": "Present when scope is `domain`."
					},
					"premium": {
						"type": "string",
						"enum": [
							"none",
							"residential",
							"stealth"
						],
						"description": "Present when scope is `domain`. A block at one tier does not imply the others."
					},
					"org": {
						"type": "string",
						"description": "Present when scope is `account`."
					},
					"expiresInMs": {
						"type": "integer",
						"minimum": 0
					},
					"consecutive": {
						"type": "integer",
						"description": "What makes the backoff exponential, and why an expired entry is still listed."
					},
					"probeTaken": {
						"type": "boolean",
						"description": "True while the single post-expiry probe request is in flight."
					}
				}
			},
			"CooldownsBody": {
				"type": "object",
				"required": [
					"cooling",
					"expired"
				],
				"properties": {
					"cooling": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/CooldownEntry"
						}
					},
					"expired": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/CooldownEntry"
						},
						"description": "Expired but still recorded: `consecutive` here is the reason the next cooldown on that key is longer."
					}
				}
			},
			"ErrorBody": {
				"type": "object",
				"required": [
					"requestId",
					"error"
				],
				"properties": {
					"requestId": {
						"type": "string"
					},
					"error": {
						"type": "object",
						"required": [
							"code",
							"class",
							"message",
							"docs"
						],
						"properties": {
							"code": {
								"description": "The outcome, or UNAUTHORIZED. One vocabulary whether the failure happened at a provider or before one was reached.",
								"type": "string"
							},
							"class": {
								"$ref": "#/components/schemas/OutcomeClass"
							},
							"message": {
								"type": "string"
							},
							"docs": {
								"type": "string",
								"format": "uri"
							}
						}
					},
					"attempts": {
						"type": "array",
						"description": "What was tried and what each provider said. The grain you need when debugging a failover.",
						"items": {
							"type": "object"
						}
					}
				}
			}
		}
	}
}
