{
  "openapi": "3.1.0",
  "info": {
    "title": "String Web Access API",
    "version": "1.0.0",
    "description": "Fetch any http or https URL, search the web, discover a site's URLs, and run a remote browser over CDP. Fetch and search are billed per request; remote browser sessions are billed by bandwidth and session time. Failed requests are not billed. See https://portal.usestring.ai/docs for guides and pricing.",
    "termsOfService": "https://usestring.ai/terms",
    "contact": {
      "name": "String support",
      "email": "support@usestring.ai",
      "url": "https://portal.usestring.ai/docs"
    }
  },
  "externalDocs": {
    "description": "String Web Access API documentation",
    "url": "https://portal.usestring.ai/docs"
  },
  "servers": [
    {
      "url": "https://request.usestring.ai/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Fetch",
      "description": "Fetch a URL, with optional JavaScript rendering, browser actions, screenshots, XHR capture and structured extraction."
    },
    {
      "name": "Search",
      "description": "Structured web search results from Google, DuckDuckGo, Brave and Mojeek, and Google AI Mode answers."
    },
    {
      "name": "Sitemap",
      "description": "Asynchronous whole-site URL discovery: submit for a quote, approve, poll status, then page through results. Every path is also served under `/map` (for example `/map/{jobId}/approve`) with the same request and response shapes."
    },
    {
      "name": "Browser",
      "description": "Remote CDP browser over WebSocket and reusable browser profiles (contexts)."
    }
  ],
  "paths": {
    "/fetch": {
      "post": {
        "operationId": "fetchUrl",
        "summary": "Fetch a URL",
        "description": "Fetch one http or https URL and return the destination's response as a JSON envelope, Markdown, or raw bytes. String picks the fetch strategy (direct request or browser, proxy class, captcha solving) automatically. Use it to read a known URL, render JavaScript, run browser actions or take a screenshot, capture a page's XHR calls, or extract fields with a JSON Schema; use searchWeb when you have a query instead of a URL.",
        "tags": [
          "Fetch"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/api-reference/fetch"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FetchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful fetch. The body shape depends on `format`, `jsonSchema`, `screenshot` and `actions`. The destination's own status is in the JSON `statusCode` or the `x-status-code` header; a destination 304 is still an HTTP 200 here.",
            "headers": {
              "x-status-code": {
                "$ref": "#/components/headers/XStatusCode"
              },
              "x-final-url": {
                "$ref": "#/components/headers/XFinalUrl"
              },
              "x-request-id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "x-request-duration-ms": {
                "$ref": "#/components/headers/XRequestDurationMs"
              },
              "x-data-transfer-bytes": {
                "$ref": "#/components/headers/XDataTransferBytes"
              },
              "x-origin-etag": {
                "$ref": "#/components/headers/XOriginEtag"
              },
              "x-origin-last-modified": {
                "$ref": "#/components/headers/XOriginLastModified"
              },
              "x-protocol-fallback-url": {
                "$ref": "#/components/headers/XProtocolFallbackUrl"
              },
              "x-billed-request-type": {
                "$ref": "#/components/headers/XBilledRequestType"
              },
              "x-json-schema-applied": {
                "$ref": "#/components/headers/XJsonSchemaApplied"
              },
              "x-json-schema-fallback-reason": {
                "$ref": "#/components/headers/XJsonSchemaFallbackReason"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/FetchResponse"
                    },
                    {
                      "title": "Extracted object",
                      "description": "With `jsonSchema` and successful extraction (`x-json-schema-applied: true`): the extracted value, shaped by your schema, with no envelope."
                    }
                  ]
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "`format: markdown` (without `screenshot`/`actions`): the page converted to Markdown, sent as `text/markdown; charset=utf-8`. Non-HTML responses are returned verbatim as text."
                }
              },
              "*/*": {
                "schema": {
                  "type": "string",
                  "format": "binary",
                  "description": "`format: raw`: the destination's original bytes, always gzip-compressed (`Content-Encoding: gzip`), with upstream headers forwarded where possible."
                }
              }
            }
          },
          "400": {
            "description": "Validation error: bad input, or a method that requires KYC for that path (`issues[].field` is `url`). Also returned when no fetch path can send the requested method or body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "description": "Either the destination domain is not enabled for your organization (body has `reason`), or the destination's own 403 passed through as a terminal origin failure (`errorType: TerminalOriginError`, body has `data`).",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/AccessDeniedResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Upstream or destination error: the destination answered with an error, could not be reached, or the request could not be completed. Read `statusCode` (the destination's status, if any) and `errorType`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "XHR capture or `waitForResponse` is at capacity or temporarily unavailable. Retry, after `Retry-After` seconds when that header is present.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "The destination did not respond in time (`errorType: TargetTimeoutError`). Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Terminal origin failure (`errorType: TerminalOriginError`): returned under the destination's own HTTP status (for example 403 or 530), with the destination's body in `data` and its headers in `headers`. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/search": {
      "post": {
        "operationId": "searchWeb",
        "summary": "Search the web",
        "description": "Run a web search on Google (default), DuckDuckGo, Brave or Mojeek and return the ranked organic results, plus Google page surfaces such as ads, local places, the knowledge panel and AI overviews. Set `engine: google_ai_mode` for Google AI Mode's answer instead. Use it when you have a query rather than a URL; fetch a result's `url` with fetchUrl to read the page.",
        "tags": [
          "Search"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/api-reference/search"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "`format: raw`: the Google results page as HTML, one page per request."
                }
              }
            }
          },
          "400": {
            "description": "Validation error: missing `query`, an invalid option, an unrecognized field, an option sent with an engine or search type that does not support it, or a `location` that cannot be placed.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "$ref": "#/components/schemas/FieldErrorsResponse"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "description": "Verification challenge that could not be cleared.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "The search could not be completed, such as a Google AI Mode answer that could not be read. Retrying may succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap": {
      "post": {
        "operationId": "createSitemapJob",
        "summary": "Submit a sitemap crawl and get a quote",
        "description": "Submit an asynchronous crawl that discovers the URLs on one hostname from a starting URL, and get a cost quote. Nothing is crawled or charged until you call approveSitemapJob, within one hour. Use it to map a site's pages; use fetchUrl to read a page. Also served under `/map` with the same shapes.",
        "tags": [
          "Sitemap"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/api-reference/sitemap#submit-a-crawl"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SitemapCreateRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Quote produced; job awaiting approval.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitemapQuote"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/DestinationNotEnabled"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "operationId": "listSitemapJobs",
        "summary": "List sitemap jobs",
        "description": "List your organization's sitemap jobs, most recent first. Use it to find a jobId or review past crawls. Also served under `/map` with the same shapes.",
        "tags": [
          "Sitemap"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/api-reference/sitemap#list-jobs"
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size.",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of jobs to skip.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Jobs returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitemapJobList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/sitemap/{jobId}/approve": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "description": "The sitemap job ID returned by createSitemapJob.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "approveSitemapJob",
        "summary": "Approve a sitemap quote and start the crawl",
        "description": "Approve a quoted sitemap job and start the crawl. The estimated cost is held against your balance and settled when the job finishes. Call it after createSitemapJob, within one hour; expired quotes are `failed` and cannot be approved. Also served under `/map` with the same shapes.",
        "tags": [
          "Sitemap"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/api-reference/sitemap#approve-a-job"
        },
        "responses": {
          "200": {
            "description": "Job already in a terminal state; returns its current status (for example `failed` for an expired quote).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitemapJob"
                }
              }
            }
          },
          "202": {
            "description": "Approved; the job is `running`. Re-approving a still-running job also returns 202.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitemapJobAck"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Insufficient balance for `estimatedCostUsd` (`error: insufficient_funds`). The job is marked `failed` and cannot be re-approved after a top-up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Job not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Approval handoff incomplete (`status: partial_state`). Retry the approve call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitemapPartialState"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "Approval failed. Also returned when approving a `canceled` or `token_cap_exceeded` job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap/{jobId}": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "description": "The sitemap job ID returned by createSitemapJob.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getSitemapJob",
        "summary": "Get sitemap job status",
        "description": "Return a sitemap job's status and progress. Poll it after approveSitemapJob until the status is `completed`, `failed`, `canceled` or `token_cap_exceeded`. Also served under `/map` with the same shapes.",
        "tags": [
          "Sitemap"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/api-reference/sitemap#get-job-status"
        },
        "responses": {
          "200": {
            "description": "Status returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitemapJob"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Job not found or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Approval handoff incomplete (`partial_state`). Retry the approve call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitemapPartialState"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "cancelSitemapJob",
        "summary": "Cancel a sitemap job",
        "description": "Cancel a sitemap job that has not finished. Use it to stop a crawl you no longer need. Also served under `/map` with the same shapes.",
        "tags": [
          "Sitemap"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/api-reference/sitemap#cancel-a-job"
        },
        "responses": {
          "200": {
            "description": "Canceled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitemapJobAck"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Job not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Job already finished, failed, or canceled. Returns its current status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitemapJob"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap/{jobId}/urls": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "description": "The sitemap job ID returned by createSitemapJob.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "listSitemapJobUrls",
        "summary": "Get discovered URLs",
        "description": "Return a page of the URLs a sitemap job discovered, with each URL's depth, parent and discovery source. It works mid-crawl and `total` grows as the crawl runs; check getSitemapJob to know when the set is complete. Also served under `/map` with the same shapes.",
        "tags": [
          "Sitemap"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/api-reference/sitemap#get-discovered-urls"
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size.",
            "schema": {
              "type": "integer",
              "default": 1000,
              "maximum": 5000
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of entries to skip.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Results page returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SitemapUrlPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Job not found or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/contexts": {
      "post": {
        "operationId": "createBrowserContext",
        "summary": "Create a browser profile",
        "description": "Create a reusable browser profile (context) that saves logins, cookies and site storage between remote browser sessions. Pass the returned `id` as `contextId` when connecting to GET /wss; closing that session saves the profile.",
        "tags": [
          "Browser"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/browser/contexts#create-a-profile"
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Retry a create without making a second profile. Scoped to your organization for 24 hours. A retry while creation is in progress returns 409; a malformed or repeated header returns 400.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._:-]{1,255}$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContextRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Profile created, or an idempotent create replayed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Context"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Insufficient balance for creation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Duplicate name, or a create with the same Idempotency-Key is still in progress (retry with backoff).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Profile limit (25 per organization), storage limit (1 GiB per organization), or request-rate limit reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "operationId": "listBrowserContexts",
        "summary": "List browser profiles",
        "description": "List your organization's browser profiles, newest first, without deleted profiles. Use it to find a `contextId`.",
        "tags": [
          "Browser"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/browser/contexts#list-profiles"
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Invalid values use the default. Stop when a page comes back short.",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of profiles to skip.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Profiles returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContextList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/contexts/{contextId}": {
      "parameters": [
        {
          "name": "contextId",
          "in": "path",
          "required": true,
          "description": "The browser profile's UUID.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "getBrowserContext",
        "summary": "Inspect a browser profile",
        "description": "Return one browser profile with its status and latest save. Use it to check whether a profile is busy (`IN_USE` or `UPLOADING`) or to confirm that a session's changes were saved.",
        "tags": [
          "Browser"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/browser/contexts#inspect-a-profile"
        },
        "responses": {
          "200": {
            "description": "Profile returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Context"
                }
              }
            }
          },
          "400": {
            "description": "Malformed profile ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Unknown, foreign-owned, or deleted profile (`error: Context not found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteBrowserContext",
        "summary": "Delete a browser profile",
        "description": "Delete a browser profile. It becomes unusable at once and its name and slot are free; stored data is removed in the background. Repeating the request returns 202 until the data is gone, then 404.",
        "tags": [
          "Browser"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/browser/contexts#delete-a-profile"
        },
        "responses": {
          "202": {
            "description": "Deletion accepted; stored data is being removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContextDeleteAccepted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Profile not found for this organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "A saving session is open on the profile. Read-only sessions do not block deletion.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/wss": {
      "get": {
        "operationId": "connectBrowser",
        "summary": "Connect to a remote CDP browser (WebSocket)",
        "description": "Open a WebSocket to a remote Chrome DevTools Protocol (CDP) browser for Playwright, Puppeteer or another CDP client, at `wss://request.usestring.ai/v1/wss`. This is a WebSocket upgrade, not a JSON endpoint, so a function-calling layer cannot call it as a plain HTTP request. Send the same `Authorization: Bearer` header when connecting. Failures arrive after the upgrade as WebSocket close frames: `1000` normal close (with persistence on, the profile saves in the background); `1008` authentication, request, quota or access refusal, including an invalid or unrecognized query parameter or a busy profile; `1011` connection failure or session limit reached (retry with backoff); `4008` session bandwidth limit reached (reconnect to continue). Sessions close after 30 minutes, or 5 minutes without traffic. Billed by bandwidth and session time.",
        "tags": [
          "Browser"
        ],
        "externalDocs": {
          "url": "https://portal.usestring.ai/docs/api-reference/browser-websocket"
        },
        "servers": [
          {
            "url": "wss://request.usestring.ai/v1",
            "description": "WebSocket endpoint."
          }
        ],
        "parameters": [
          {
            "name": "proxy",
            "in": "query",
            "required": false,
            "description": "Proxy tier for the session's traffic: `premium` (residential) or `standard` (datacenter, half the premium bandwidth rate).",
            "schema": {
              "type": "string",
              "enum": [
                "premium",
                "standard"
              ],
              "default": "premium"
            }
          },
          {
            "name": "countryCode",
            "in": "query",
            "required": false,
            "description": "ISO 3166-1 alpha-2 country the session's traffic exits from. Normalized to uppercase. Profiles do not remember it, so send the same value on every connection that uses a `contextId`.",
            "schema": {
              "type": "string",
              "default": "US"
            }
          },
          {
            "name": "contextId",
            "in": "query",
            "required": false,
            "description": "UUID of a browser profile to restore into the session. Only one saving session can use a profile at a time.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "persist",
            "in": "query",
            "required": false,
            "description": "With `contextId`, save the profile when the session closes. `false` restores it and discards changes; any number of read-only sessions can run at once. Requires `contextId`; a profile that has never been saved cannot be used with `persist=false`.",
            "schema": {
              "type": "boolean",
              "default": true
            }
          }
        ],
        "responses": {
          "101": {
            "description": "WebSocket upgrade accepted. This alone does not mean authentication succeeded: check for a close frame (code 1008) before assuming the session is ready."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key",
        "description": "Send `Authorization: Bearer YOUR_API_KEY`. Get an API key at https://portal.usestring.ai/settings. The same header authenticates the /wss WebSocket."
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "description": "Error body used by most error responses. Errors are always JSON, whatever `format` the request asked for. Key off `statusCode` and `errorType`, not the HTTP status alone. See https://portal.usestring.ai/docs/api-reference/errors",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Error type identifier, for example `Validation error` or `Request failed`."
          },
          "message": {
            "type": "string",
            "description": "Human-readable error message."
          },
          "statusCode": {
            "type": "integer",
            "description": "The status the destination returned, when the request reached it. Not the HTTP status of this response. Absent when no response came back from the destination (absent means no response, not 0). Can be a success status, for example an anti-bot challenge served as 200 and reported on a 502."
          },
          "errorType": {
            "type": "string",
            "description": "Machine-readable failure class, sent when the upstream failure can be identified. `TargetCertificateError`: the destination's TLS certificate cannot be verified (only with `ignoreCertificateErrors: false`). `TooManyRedirectsError`: the redirect chain does not terminate; do not retry. `TerminalOriginError`: the destination's own configuration produced the answer; do not retry, read `data`. `TargetTimeoutError`: the destination did not respond in time (HTTP 504); retry later. `TargetUnreachableError`: could not connect, or the connection dropped before an answer. `TargetHostNotFoundError`: the hostname has no DNS record (HTTP 502); do not retry until it resolves.",
            "enum": [
              "TargetCertificateError",
              "TooManyRedirectsError",
              "TerminalOriginError",
              "TargetTimeoutError",
              "TargetUnreachableError",
              "TargetHostNotFoundError"
            ]
          },
          "traceId": {
            "type": "string",
            "description": "Optional trace identifier."
          },
          "issues": {
            "type": "array",
            "description": "Per-field validation issues.",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string",
                  "description": "The request field the issue is about."
                },
                "message": {
                  "type": "string",
                  "description": "What is wrong with the field."
                }
              }
            }
          },
          "data": {
            "description": "The destination's own response body. Present only on a `TerminalOriginError`, where that body is the answer."
          },
          "headers": {
            "type": "object",
            "description": "The destination's own response headers. Present exactly when `data` is."
          },
          "finalUrl": {
            "type": "string",
            "description": "`/fetch` only: the URL the response was served from after any redirects, or the requested URL. Sent on `/fetch` error responses too, except when the request body has no valid `url`."
          }
        }
      },
      "AccessDeniedResponse": {
        "type": "object",
        "description": "403 body when the destination domain is not enabled for your organization. Email support@usestring.ai to request access. See https://portal.usestring.ai/docs/fetch/access-control",
        "required": [
          "reason"
        ],
        "properties": {
          "reason": {
            "type": "string",
            "description": "Why the request was refused, for example `Requests to example.com are not allowed. Contact support@usestring.ai for access.`"
          }
        }
      },
      "RateLimitError": {
        "description": "429 body. See https://portal.usestring.ai/docs/api-reference/rate-limits",
        "allOf": [
          {
            "$ref": "#/components/schemas/ErrorResponse"
          },
          {
            "type": "object",
            "properties": {
              "limitRequestsPerSecond": {
                "type": "integer",
                "description": "Your organization's allowed average requests per second."
              }
            }
          }
        ]
      },
      "FieldErrorsResponse": {
        "type": "object",
        "description": "Validation error shape that `/search` returns for some requests, for example `searchCount` sent with `format: raw`.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Error identifier, for example `Invalid request`."
          },
          "details": {
            "type": "object",
            "properties": {
              "formErrors": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Errors not tied to one field."
              },
              "fieldErrors": {
                "type": "object",
                "description": "Errors keyed by field name, each a list of messages.",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "XhrFilter": {
        "type": "object",
        "description": "A `captureXHR.include` filter. A request matches when it meets every condition the filter sets. See https://portal.usestring.ai/docs/fetch/xhr-capture#filter-with-include",
        "additionalProperties": false,
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Pattern matched against the full request URL with its query string."
          },
          "match": {
            "type": "string",
            "enum": [
              "contains",
              "glob",
              "regex"
            ],
            "default": "contains",
            "description": "How `url` is matched: `contains` is a case-sensitive substring; `glob` matches the whole URL (`*` any run, `?` one character); `regex` is an RE2 expression that may match anywhere."
          },
          "methods": {
            "type": "array",
            "description": "Match only these methods. Any case is accepted. Default: any method.",
            "items": {
              "type": "string",
              "enum": [
                "GET",
                "POST",
                "PUT",
                "PATCH",
                "DELETE",
                "HEAD",
                "OPTIONS"
              ]
            }
          },
          "statusCodes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "description": "Match only responses with one of these statuses. A failed request, or one with no response yet, never matches. Default: any status.",
            "items": {
              "type": "integer",
              "minimum": 100,
              "maximum": 599
            }
          }
        }
      },
      "CaptureXhrOptions": {
        "type": "object",
        "description": "`{}` captures every XHR and fetch() request; `{ \"include\": [...] }` captures only requests that match at least one filter.",
        "properties": {
          "include": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/XhrFilter"
            },
            "description": "1 to 10 filters."
          }
        }
      },
      "BrowserAction": {
        "type": "object",
        "description": "One browser action. Each action has a `type` and fields of its own; this schema checks only `type`. Fields by type: `navigate`: `url`. `click`: `selector`, optional `all` (true clicks every match). `write`: `text` (types into the focused element). `press`: `key` (for example `Enter`, `Tab`). `hover`: `selector`. `scroll`: optional `direction` (`up`/`down`/`left`/`right`), `amount`, `selector`. `selectOption`: `selector`, `value` (string or array of strings). `wait`: optional `milliseconds` (max 30000), `selector`, `timeout` (max 30000). `screenshot`: optional `full_page`, `quality` (1-100); at most one per request. `waitForResponse`: `url` (required), optional `match` (`contains`/`glob`/`regex`, default `contains`), `methods`, `statusCodes`, `timeout` (0-30000, default 30000), `onTimeout` (`fail`/`continue`, default `fail`), `asResult` (default false; at most one action may set it). The whole sequence must finish within 180 seconds. See https://portal.usestring.ai/docs/screenshots/browser-actions and https://portal.usestring.ai/docs/fetch/xhr-capture#wait-for-a-response",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "navigate",
              "click",
              "write",
              "press",
              "hover",
              "scroll",
              "selectOption",
              "wait",
              "screenshot",
              "waitForResponse"
            ],
            "description": "The action to run."
          }
        }
      },
      "FetchRequest": {
        "type": "object",
        "description": "Request body for POST /fetch. Unknown fields are rejected with a 400; `url` is the only required field. Field rules: `body` is forbidden on GET and HEAD. `jsonSchema` requires `format: json`. `headers` cannot be combined with `executeJS` or `requireWSS`. `captureXHR` requires `executeJS: true` and cannot be combined with `jsonSchema`; without `actions` or `screenshot` it also requires `format: json`. `screenshot` and `actions` are mutually exclusive; with either, `format: raw`, a non-GET `method`, `body`, `headers` and `solveCaptcha: false` are not supported. `jsonSchema` works with `actions` unless the request also takes a screenshot.",
        "additionalProperties": false,
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The http or https URL to fetch."
          },
          "method": {
            "type": "string",
            "enum": [
              "GET",
              "POST",
              "PUT",
              "PATCH",
              "DELETE",
              "HEAD",
              "OPTIONS"
            ],
            "default": "GET",
            "description": "HTTP method. Accepted in any case (`get` and `Post` work). Any other method returns a 400. A non-GET method or a `body` is sent only on fetch paths that deliver it exactly; if none can, the request returns a 400 before anything is sent. A write (POST, PUT, PATCH, DELETE) reaches the destination at most once, so an error on a write does not prove the destination did not act on it."
          },
          "body": {
            "type": [
              "string",
              "object"
            ],
            "description": "Request body to send to the destination. Forbidden on GET and HEAD. A string is sent as given; an object is sent as the exact JSON text you wrote."
          },
          "format": {
            "type": "string",
            "enum": [
              "json",
              "raw",
              "markdown"
            ],
            "default": "json",
            "description": "Response format. `json`: an envelope with the destination's status, headers, parsed body and final URL. `raw`: the original bytes, gzip-compressed, with the destination status in `x-status-code`. `markdown`: HTML converted to Markdown (`text/markdown`). See https://portal.usestring.ai/docs/fetch/response-formats"
          },
          "markdownMode": {
            "type": "string",
            "enum": [
              "readable",
              "full"
            ],
            "default": "full",
            "description": "Markdown preservation level. `full` converts the whole cleaned page, navigation and footers included; `readable` removes boilerplate for a smaller output. Ignored unless `format` is `markdown`."
          },
          "mainContentOnly": {
            "type": "boolean",
            "default": false,
            "description": "Remove page chrome before conversion. Ignored unless `format` is `markdown`."
          },
          "jsonSchema": {
            "type": "object",
            "description": "A JSON Schema to extract into with AI. On success the response body is the extracted object itself. Must be a valid JSON Schema object (shorthand such as `{\"title\": \"string\"}` is a 400). Requires `format: json`. Limits: nesting depth 10, 200 keys, 50 KB serialized. Adds a dynamic extraction fee. See https://portal.usestring.ai/docs/fetch/structured-extraction"
          },
          "executeJS": {
            "type": "boolean",
            "description": "Render the page in a browser. Incompatible with `headers`. See https://portal.usestring.ai/docs/fetch/javascript-rendering"
          },
          "requireWSS": {
            "type": "boolean",
            "description": "Require a browser WebSocket fetch path. Incompatible with `headers`."
          },
          "captureXHR": {
            "description": "Return the XHR and fetch() requests the page makes, as `xhr`. `true` (or `{}`) captures all of them; `{ \"include\": [...] }` captures only those that match a filter. Requires `executeJS: true`. See https://portal.usestring.ai/docs/fetch/xhr-capture",
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "$ref": "#/components/schemas/CaptureXhrOptions"
              }
            ]
          },
          "headers": {
            "type": "object",
            "description": "Custom HTTP headers to forward (max 50). Direct (request-based) fetch only. Names are case-insensitive and may not repeat in a different case. Values may not contain control characters such as CR, LF or NUL, or characters above U+00FF. Service-managed headers cannot be set: proxy-authorization, proxy-authenticate, proxy-connection, connection, transfer-encoding, content-length, te, upgrade, keep-alive, trailer, host. See https://portal.usestring.ai/docs/fetch/custom-headers",
            "maxProperties": 50,
            "propertyNames": {
              "minLength": 1,
              "maxLength": 256,
              "pattern": "^[A-Za-z0-9!#$%&'*+.^_`|~-]+$"
            },
            "additionalProperties": {
              "type": "string",
              "maxLength": 32768
            }
          },
          "countryCode": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "ISO 3166-1 alpha-2 country for the proxy exit. See https://portal.usestring.ai/docs/fetch/proxies-and-geolocation"
          },
          "solveCaptcha": {
            "type": "boolean",
            "default": true,
            "description": "Solve captcha challenges. `false` fails on a challenge instead. See https://portal.usestring.ai/docs/fetch/captcha-solving"
          },
          "ignoreCertificateErrors": {
            "type": "boolean",
            "default": true,
            "description": "Skip TLS certificate verification (the default). Send `false` to require a valid certificate; a bad certificate then fails with `TargetCertificateError`. Send `false` for credentials, payments, or data you will treat as authoritative."
          },
          "screenshot": {
            "type": "boolean",
            "description": "Capture a screenshot, returned as base64 in the JSON body. Shorthand for one `screenshot` action. Mutually exclusive with `actions`. Billed as a browser session. See https://portal.usestring.ai/docs/screenshots/overview"
          },
          "actions": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/BrowserAction"
            },
            "description": "Browser actions to run, in order, after the API loads `url` (1 to 50). Mutually exclusive with `screenshot`. Billed as a browser session. See https://portal.usestring.ai/docs/screenshots/browser-actions"
          }
        }
      },
      "XhrEntry": {
        "type": "object",
        "description": "One captured XHR or fetch() request. See https://portal.usestring.ai/docs/fetch/xhr-capture#the-xhr-entry",
        "properties": {
          "resourceType": {
            "type": "string",
            "enum": [
              "xhr",
              "fetch"
            ],
            "description": "Whether the page used XMLHttpRequest or fetch()."
          },
          "request": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "description": "The full request URL, query string included."
              },
              "method": {
                "type": "string",
                "description": "The request method, such as GET or POST."
              },
              "headers": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "description": "The request headers, each value a single string."
              },
              "body": {
                "type": "string",
                "description": "The body the page sent, encoded as `bodyEncoding` says. Absent when there was no body or it was omitted."
              },
              "bodyEncoding": {
                "type": "string",
                "enum": [
                  "utf8",
                  "base64"
                ],
                "description": "`utf8` for valid UTF-8 text, `base64` for anything else."
              },
              "bodyOmitted": {
                "type": "string",
                "enum": [
                  "too_large",
                  "budget_exhausted",
                  "unavailable"
                ],
                "description": "Why `body` is absent."
              }
            }
          },
          "response": {
            "type": "object",
            "description": "Absent when no response arrived.",
            "properties": {
              "statusCode": {
                "type": "number",
                "description": "The response status."
              },
              "headers": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "description": "The response headers, each value a single string."
              },
              "body": {
                "type": "string",
                "description": "The response body, decoded from any content-encoding and encoded as `bodyEncoding` says."
              },
              "bodyEncoding": {
                "type": "string",
                "enum": [
                  "utf8",
                  "base64"
                ],
                "description": "`utf8` for valid UTF-8 text, `base64` for anything else."
              },
              "bodyOmitted": {
                "type": "string",
                "enum": [
                  "too_large",
                  "budget_exhausted",
                  "unavailable"
                ],
                "description": "Why `body` is absent."
              }
            }
          },
          "error": {
            "type": "string",
            "description": "The network error, when the request failed."
          },
          "matchedFilter": {
            "type": "number",
            "description": "With `captureXHR.include`: the 0-based index of the first filter this request matched."
          },
          "timing": {
            "type": "object",
            "properties": {
              "startedAt": {
                "type": "number",
                "description": "When the page sent the request, in milliseconds since the Unix epoch."
              },
              "durationMs": {
                "type": "number",
                "description": "Milliseconds until the response finished loading or the request failed. Absent while in flight."
              }
            }
          }
        }
      },
      "FetchResponse": {
        "type": "object",
        "title": "Fetch JSON envelope",
        "description": "The `format: json` response body. Also the envelope for `screenshot`/`actions` requests (whatever `format`), and the `jsonSchema` fallback envelope (`extracted: false`). A destination `304 Not Modified` is still an HTTP 200 here, with `statusCode: 304`.",
        "properties": {
          "statusCode": {
            "type": "integer",
            "description": "The destination's HTTP status code."
          },
          "headers": {
            "type": "object",
            "description": "The destination's response headers, filtered. Values can be arrays of strings.",
            "additionalProperties": {
              "type": [
                "string",
                "array"
              ],
              "items": {
                "type": "string"
              }
            }
          },
          "data": {
            "description": "The response body. Parsed JSON when the destination returned JSON, otherwise text. With `screenshot`/`actions`: the final page as HTML, or Markdown with `format: markdown`; when the sequence succeeded and a `waitForResponse` action with `asResult` matched, the matched response's body as a string instead."
          },
          "finalUrl": {
            "type": "string",
            "description": "The URL the response was served from after any redirects (or after all actions ran), or the requested URL if there was no redirect."
          },
          "screenshot": {
            "type": "object",
            "description": "Present only if a screenshot was taken.",
            "properties": {
              "format": {
                "type": "string",
                "description": "Image format, for example `png`."
              },
              "base64": {
                "type": "string",
                "description": "The image, base64-encoded."
              }
            }
          },
          "matchedResponse": {
            "type": "object",
            "description": "Present only when the action sequence succeeded and its `asResult` action matched. Describes the response now in `data`.",
            "properties": {
              "url": {
                "type": "string"
              },
              "method": {
                "type": "string"
              },
              "statusCode": {
                "type": "integer"
              },
              "headers": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "bodyEncoding": {
                "type": "string",
                "enum": [
                  "utf8",
                  "base64"
                ],
                "description": "When `base64`, decode `data` first."
              }
            }
          },
          "xhr": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/XhrEntry"
            },
            "description": "Present only with `captureXHR`: the captured requests, in the order the page sent them. Empty when nothing qualified."
          },
          "xhrTruncated": {
            "type": "boolean",
            "description": "`true` when a capture limit left requests or bodies out of `xhr`. Absent otherwise."
          },
          "error": {
            "type": "string",
            "description": "Present if the browser action sequence failed (the response is still HTTP 200), for example `Browser action sequence failed`."
          },
          "failedActionIndex": {
            "type": "integer",
            "description": "0-based index into `actions` of the action that failed."
          },
          "extracted": {
            "type": "boolean",
            "description": "`false` on a `jsonSchema` fallback: extraction did not satisfy the schema and `data` holds the unextracted page."
          },
          "reason": {
            "type": "string",
            "enum": [
              "schema_mismatch",
              "unparseable_output",
              "llm_error",
              "body_too_large",
              "empty_body"
            ],
            "description": "`jsonSchema` fallback reason. `schema_mismatch`: a required field was not found; retrying will not help. `unparseable_output` and `llm_error`: usually transient, worth a retry. `body_too_large`: extraction skipped. `empty_body`: nothing to extract."
          }
        }
      },
      "SearchCoordinates": {
        "type": "object",
        "description": "An exact point to search from. Takes precedence over `location`.",
        "required": [
          "latitude",
          "longitude"
        ],
        "properties": {
          "latitude": {
            "type": "number",
            "minimum": -90,
            "maximum": 90,
            "description": "Decimal degrees."
          },
          "longitude": {
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "description": "Decimal degrees."
          },
          "radius": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000,
            "default": 5000,
            "description": "How precise the point is, in meters."
          }
        }
      },
      "SearchDateRange": {
        "description": "Publication window. A preset (`hour`, `day`, `week`, `month`, `year` for the past hour through the past year), or a custom range of ISO calendar dates. `google` engine only; `web`, `videos` and `forums` search types only.",
        "oneOf": [
          {
            "type": "string",
            "enum": [
              "hour",
              "day",
              "week",
              "month",
              "year"
            ]
          },
          {
            "type": "object",
            "description": "Custom range. Needs `from`, `to` or both, and `from` must not be after `to`.",
            "minProperties": 1,
            "properties": {
              "from": {
                "type": "string",
                "format": "date",
                "description": "First day of the range, inclusive (YYYY-MM-DD)."
              },
              "to": {
                "type": "string",
                "format": "date",
                "description": "Last day of the range, inclusive (YYYY-MM-DD)."
              }
            }
          }
        ]
      },
      "SearchRequest": {
        "type": "object",
        "description": "Request body for POST /search. Any other field is rejected with a 400.",
        "additionalProperties": false,
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "The search query to run."
          },
          "engine": {
            "type": "string",
            "enum": [
              "google",
              "duckduckgo",
              "brave",
              "mojeek",
              "google_ai_mode"
            ],
            "default": "google",
            "description": "Search engine. `google_ai_mode` returns Google's AI Mode answer in `aiMode` instead of ranked results."
          },
          "country": {
            "type": "string",
            "default": "US",
            "description": "ISO 3166-1 alpha-2 country code used to localize results. Case-insensitive. Applies to every engine."
          },
          "language": {
            "type": "string",
            "description": "Language tag for result language, such as `en` or `pt-br`. Applies to every engine."
          },
          "searchCount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 300,
            "description": "Number of organic results wanted. Google is paged until that many are collected (each page that answered bills as one search); the response then includes `paging`. With `page` N set, the maximum is 300 - (N-1)*10. Rejected with `format: raw` and with `google_ai_mode`. Without it, one results page is returned (about 8 to 10 Google results)."
          },
          "location": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Place name to search from, such as `London` or `Paris,Texas,United States`. `google` and `google_ai_mode` only. A name that cannot be placed is a 400; send `coordinates` instead."
          },
          "coordinates": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SearchCoordinates"
              }
            ],
            "description": "Point to search from. `google` and `google_ai_mode` only. Wins over `location` if both are sent."
          },
          "page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "default": 1,
            "description": "Google results page to start from; page N is the page Google shows as N. On `images` the range is 1 to 3; on `shopping` it must be 1. A page past the last result returns empty `results` with `zeroResults: true`. `google` engine only; sending it with another engine (Google AI Mode included) is a 400."
          },
          "dateRange": {
            "$ref": "#/components/schemas/SearchDateRange"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "relevance",
              "date"
            ],
            "default": "relevance",
            "description": "Result order. `date` puts the newest first. `web`, `videos` and `forums` search types only. `google` engine only; sending it with another engine (Google AI Mode included) is a 400."
          },
          "format": {
            "type": "string",
            "enum": [
              "structured",
              "raw"
            ],
            "default": "structured",
            "description": "`structured` returns results as JSON (recommended). `raw` returns the Google results page as HTML (`text/html`), one page per request; choose the page with `page`. `raw` applies to the `web` search type only. `google` engine only; sending it with another engine (Google AI Mode included) is a 400."
          },
          "aiOverview": {
            "type": "boolean",
            "default": false,
            "description": "Make a second, sequential fetch to fill an AI Overview that Google streams in after the page loads. Adds latency and bills as two searches. Without it, a streamed overview comes back as `declined: true`. `web` search type only. `google` engine only; sending it with another engine (Google AI Mode included) is a 400."
          },
          "searchType": {
            "type": "string",
            "enum": [
              "web",
              "images",
              "videos",
              "shopping",
              "books",
              "places",
              "forums"
            ],
            "default": "web",
            "description": "Google tab to search. `images` answers in `images`, `places` in `places`, `shopping` in `products` (with `results` empty); the other tabs answer in `results`. `google` engine only; sending it with another engine (Google AI Mode included) is a 400."
          },
          "safeSearch": {
            "type": "boolean",
            "default": false,
            "description": "`true` removes explicit results (Google SafeSearch). `google` engine only; sending it with another engine (Google AI Mode included) is a 400."
          },
          "includeOmittedResults": {
            "type": "boolean",
            "default": false,
            "description": "`true` includes results Google normally hides as very similar to ones already shown. `google` engine only; sending it with another engine (Google AI Mode included) is a 400."
          },
          "autocorrect": {
            "type": "boolean",
            "default": true,
            "description": "`false` searches the query exactly as sent instead of Google's corrected spelling. `google` engine only; sending it with another engine (Google AI Mode included) is a 400."
          },
          "restrictCountry": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 code. Returns only pages from that country (unlike `country`, which sets where the search is made from). `google` engine only; sending it with another engine (Google AI Mode included) is a 400."
          },
          "verbatim": {
            "type": "boolean",
            "default": false,
            "description": "`true` matches the query's words exactly, without synonyms or variations. `web`, `videos` and `forums` search types only. `google` engine only; sending it with another engine (Google AI Mode included) is a 400."
          }
        }
      },
      "SearchResult": {
        "type": "object",
        "description": "One ranked web document. Nothing else is ever placed in `results`.",
        "properties": {
          "position": {
            "type": "integer",
            "description": "1-based place in this response's `results`. Continues across pages on a `searchCount` request."
          },
          "rank": {
            "type": "integer",
            "description": "Absolute Google rank (page offset plus place on that page); can skip numbers. Google only."
          },
          "title": {
            "type": "string",
            "description": "Result title."
          },
          "url": {
            "type": "string",
            "description": "The page the result links to, without redirect or tracking parameters. Safe to fetch. Absent when Google linked the result only through an unknown redirect."
          },
          "displayUrl": {
            "type": "string",
            "description": "Google's displayed URL line. Cosmetic; empty when Google shows none. Not guaranteed to be fetchable."
          },
          "displayText": {
            "type": "string",
            "description": "The source line Google shows under the title, verbatim. Display text, not a URL or number. Google only."
          },
          "source": {
            "type": "string",
            "description": "Site name Google shows beside the result, such as `Reddit · r/buildapc`. Optional; Google only."
          },
          "snippet": {
            "type": "string",
            "description": "Result description text."
          },
          "video": {
            "type": "object",
            "description": "With `searchType: videos`. Each field optional.",
            "properties": {
              "channel": {
                "type": "string",
                "description": "The publisher."
              },
              "platform": {
                "type": "string",
                "description": "Such as `YouTube`."
              },
              "duration": {
                "type": "string",
                "description": "Such as `12:48`."
              }
            }
          },
          "book": {
            "type": "object",
            "description": "With `searchType: books`. Each field optional.",
            "properties": {
              "authors": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Author names."
              },
              "published": {
                "description": "The year or date of publication."
              }
            }
          }
        }
      },
      "SearchPaging": {
        "type": "object",
        "description": "How a `searchCount` request was filled. Present only when `searchCount` was sent.",
        "properties": {
          "pages": {
            "type": "integer",
            "description": "Number of results pages that answered."
          },
          "complete": {
            "type": "boolean",
            "description": "`false` when paging stopped early (`page_failed` or `deadline`); the response is still a 200 with the results collected so far."
          },
          "stoppedBy": {
            "type": "string",
            "enum": [
              "search_count",
              "end_of_results",
              "page_cap",
              "page_failed",
              "deadline"
            ],
            "description": "Why paging stopped: `search_count` (count reached), `end_of_results` (no more results), `page_cap` (36-page limit), `page_failed` (a later page failed), `deadline` (45-second budget ran out)."
          }
        }
      },
      "SearchSpelling": {
        "type": "object",
        "description": "Google's correction or suggestion for the query.",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "substituted",
              "suggested"
            ],
            "description": "`substituted`: results are for the corrected query. `suggested`: Google only offered it; results are for the query as typed."
          },
          "query": {
            "type": "string",
            "description": "The corrected text."
          },
          "asked": {
            "type": "string",
            "description": "The original query."
          }
        }
      },
      "SearchAd": {
        "type": "object",
        "description": "A sponsored result, kept apart from `results`.",
        "properties": {
          "position": {
            "type": "integer",
            "description": "1-based order within the page's ad blocks."
          },
          "format": {
            "type": "string",
            "enum": [
              "text",
              "shopping"
            ]
          },
          "block": {
            "type": "string",
            "description": "Where the ad sat, such as `top` or `bottom`."
          },
          "title": {
            "type": "string",
            "description": "Ad headline. Optional."
          },
          "url": {
            "type": "string",
            "description": "The advertiser's destination. Absent when Google hid it."
          },
          "displayUrl": {
            "type": "string",
            "description": "Address as shown. Optional."
          },
          "advertiser": {
            "type": "string",
            "description": "Advertiser name as shown. Optional."
          }
        }
      },
      "SearchPlace": {
        "type": "object",
        "description": "A business listing from a local pack, or with `searchType: places` the whole answer.",
        "properties": {
          "position": {
            "type": "integer",
            "description": "1-based order of the listing."
          },
          "name": {
            "type": "string",
            "description": "Business name."
          },
          "category": {
            "type": "string",
            "description": "Business category as Google labels it. Optional."
          },
          "rating": {
            "type": "number",
            "description": "Star rating out of 5. Optional."
          },
          "reviews": {
            "type": "integer",
            "description": "Review count as Google displays it; rounded past a thousand. Optional."
          },
          "address": {
            "type": "string",
            "description": "Street address line. Optional."
          },
          "phone": {
            "type": "string",
            "description": "Phone number as shown. Optional."
          },
          "hours": {
            "type": "string",
            "description": "Opening-hours line as shown. Optional."
          },
          "url": {
            "type": "string",
            "description": "The business's own website. Optional."
          },
          "mapsUrl": {
            "type": "string",
            "description": "Google Maps directions link; its path carries the full postal address. Optional."
          }
        }
      },
      "SearchProduct": {
        "type": "object",
        "description": "A product listing (`searchType: shopping`). Has no `url`.",
        "properties": {
          "position": {
            "type": "integer",
            "description": "1-based place in `products`."
          },
          "title": {
            "type": "string",
            "description": "Product name."
          },
          "productId": {
            "type": "string",
            "description": "Google's product ID. Optional."
          },
          "price": {
            "type": "string",
            "description": "Price as displayed, currency symbol included. Optional."
          },
          "originalPrice": {
            "type": "string",
            "description": "Price before a reduction. Present only when on sale."
          },
          "merchant": {
            "type": "string",
            "description": "The seller the listing names. Optional."
          },
          "moreMerchants": {
            "type": "boolean",
            "description": "`true` when other sellers offer the product too. Optional."
          },
          "delivery": {
            "type": "string",
            "description": "Delivery terms as displayed. Optional."
          },
          "returns": {
            "type": "string",
            "description": "Return terms as displayed. Optional."
          },
          "rating": {
            "type": "number",
            "description": "Average rating out of 5. Optional."
          },
          "reviews": {
            "type": "integer",
            "description": "Number of reviews, rounded past a thousand. Optional."
          }
        }
      },
      "SearchEntity": {
        "type": "object",
        "description": "The knowledge panel for the one business or person the query named.",
        "properties": {
          "title": {
            "type": "string",
            "description": "The entity's name."
          },
          "subtitle": {
            "type": "string",
            "description": "The type line under the name. Optional."
          },
          "description": {
            "type": "string",
            "description": "The panel's summary paragraph. Optional."
          },
          "descriptionSource": {
            "type": "object",
            "description": "Where the description came from.",
            "properties": {
              "name": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "description": "Resolved URL, when there is one."
              }
            }
          },
          "rating": {
            "type": "number",
            "description": "Star rating out of 5. Optional."
          },
          "reviews": {
            "type": "integer",
            "description": "Review count as displayed. Optional."
          },
          "website": {
            "type": "object",
            "description": "The panel's Website button.",
            "properties": {
              "url": {
                "type": "string",
                "description": "Absent when the button led nowhere readable."
              }
            }
          },
          "attributes": {
            "type": "array",
            "description": "Labelled facts in panel order.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Google attribute id, when the row carried one."
                },
                "label": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              }
            }
          },
          "profiles": {
            "type": "array",
            "description": "Social-media links from the panel.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                }
              }
            }
          },
          "unread": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Attribute ids of panel modules that were not read. Empty when the whole card was read."
          }
        }
      },
      "SearchImage": {
        "type": "object",
        "description": "An image. With `searchType: images` all fields can appear; on a `web` search (the Images block) only `position`, `title`, `url` and `source`.",
        "properties": {
          "position": {
            "type": "integer",
            "description": "1-based place in `images`."
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "The page the image appears on."
          },
          "source": {
            "type": "string",
            "description": "The site's name. Optional."
          },
          "imageUrl": {
            "type": "string",
            "description": "The image file itself. Optional."
          },
          "imageWidth": {
            "type": "integer",
            "description": "Width in pixels. Optional."
          },
          "imageHeight": {
            "type": "integer",
            "description": "Height in pixels. Optional."
          },
          "thumbnail": {
            "type": "string",
            "description": "Google's resized copy. Optional."
          }
        }
      },
      "SearchVideo": {
        "type": "object",
        "description": "An entry of the Videos or Short videos block.",
        "required": [
          "position",
          "title"
        ],
        "properties": {
          "position": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "When the destination is known."
          },
          "platform": {
            "type": "string"
          },
          "channel": {
            "type": "string"
          },
          "date": {
            "type": "string"
          },
          "duration": {
            "type": "string"
          }
        }
      },
      "SearchDiscussion": {
        "type": "object",
        "description": "A thread from the Discussions and forums block.",
        "required": [
          "position",
          "title"
        ],
        "properties": {
          "position": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "When the destination is known."
          },
          "site": {
            "type": "string"
          },
          "forum": {
            "type": "string"
          },
          "comments": {
            "type": "string",
            "description": "As displayed, such as `140+ comments`."
          },
          "age": {
            "type": "string"
          },
          "excerpt": {
            "type": "string"
          }
        }
      },
      "SearchSitelink": {
        "type": "object",
        "description": "A sub-page listed under a navigational result.",
        "required": [
          "position",
          "title"
        ],
        "properties": {
          "position": {
            "type": "integer"
          },
          "resultPosition": {
            "type": "integer",
            "description": "The rank of the result the sub-page sits under."
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "When the destination is known."
          },
          "description": {
            "type": "string"
          }
        }
      },
      "SearchOverview": {
        "type": "object",
        "description": "A Google-generated summary. The query's own overview comes first with no `topic`; entries with `topic` and `question` are Things to know tabs. A summary is never a result.",
        "properties": {
          "topic": {
            "type": "string",
            "description": "The tab's title. Absent on the query's own overview."
          },
          "question": {
            "type": "string",
            "description": "The sub-question a tab answers. Absent on the query's own overview."
          },
          "declined": {
            "type": "boolean",
            "description": "`true` when Google rendered the frame and did not answer (or the overview was streamed and `aiOverview` was not set). Absent otherwise."
          },
          "text": {
            "type": "string",
            "description": "The summary as plain text; list items start with `- `."
          },
          "sources": {
            "type": "array",
            "description": "Cited pages, in order.",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "description": "Absent when the citation could not be resolved."
                }
              }
            }
          }
        }
      },
      "SearchAnswers": {
        "type": "object",
        "description": "Single-purpose Google widgets. Each key is present only when the page carried that widget. See https://portal.usestring.ai/docs/api-reference/search#answer-widgets",
        "properties": {
          "localTime": {
            "type": "object"
          },
          "currency": {
            "type": "object"
          },
          "unitConversion": {
            "type": "object",
            "description": "Carries `value`, `from`, `result` and `to`."
          },
          "weather": {
            "type": "object",
            "description": "Current conditions and a `forecast` list."
          },
          "translation": {
            "type": "object",
            "description": "Source and translated text and, when shown, a `dictionary`."
          },
          "sports": {
            "type": "object",
            "description": "A `team` and its `games`."
          },
          "flights": {
            "type": "object",
            "description": "The route and its `options`."
          }
        }
      },
      "AiModeBlock": {
        "type": "object",
        "description": "One block of the AI Mode answer. `type` decides the other fields: `paragraph`: `text`; `heading`: `text`, `level`; `list`: `items`, `ordered`; `table`: `header`, `rows`; `code`: `text`, optional `language`.",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "paragraph",
              "heading",
              "list",
              "table",
              "code"
            ]
          },
          "text": {
            "type": "string"
          },
          "level": {
            "type": "integer",
            "description": "Heading level."
          },
          "items": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "One string per list item."
          },
          "ordered": {
            "type": "boolean",
            "description": "`true` for a numbered list."
          },
          "header": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Column names."
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "One array per row, one cell per column."
          },
          "language": {
            "type": "string",
            "description": "Code block language, when labelled."
          }
        }
      },
      "AiModeAnswer": {
        "type": "object",
        "description": "Google AI Mode's answer (`engine: google_ai_mode`). Every field except `text` and `sources` is absent when the answer has none.",
        "required": [
          "text",
          "sources"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "The answer as plain text in reading order."
          },
          "markdown": {
            "type": "string",
            "description": "The same answer as Markdown."
          },
          "blocks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AiModeBlock"
            },
            "description": "The answer's structure in reading order."
          },
          "sources": {
            "type": "array",
            "description": "Pages the answer cites, in order. Empty when Google cited none.",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string",
                  "description": "Optional."
                },
                "url": {
                  "type": "string",
                  "description": "Absent when the citation could not be resolved."
                },
                "snippet": {
                  "type": "string",
                  "description": "The quoted passage. Optional."
                },
                "source": {
                  "type": "string",
                  "description": "The site's name. Optional."
                }
              }
            }
          },
          "products": {
            "type": "array",
            "description": "Products the answer recommends.",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string",
                  "description": "Product name."
                },
                "price": {
                  "type": "string",
                  "description": "As displayed. Optional."
                },
                "oldPrice": {
                  "type": "string",
                  "description": "Price before a reduction. Present only on sale."
                },
                "merchant": {
                  "type": "string",
                  "description": "Optional."
                },
                "moreSellers": {
                  "type": "boolean",
                  "description": "Optional."
                },
                "rating": {
                  "type": "number",
                  "description": "Optional."
                },
                "reviews": {
                  "type": "integer",
                  "description": "Optional."
                },
                "thumbnail": {
                  "type": "string",
                  "description": "Optional."
                },
                "productId": {
                  "type": "string",
                  "description": "Google's product ID. Optional."
                },
                "url": {
                  "type": "string",
                  "description": "Google's page for the product. Optional."
                }
              }
            }
          },
          "places": {
            "type": "array",
            "description": "Businesses and places the answer names.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "category": {
                  "type": "string",
                  "description": "Optional."
                },
                "rating": {
                  "type": "number",
                  "description": "Optional."
                },
                "reviews": {
                  "type": "integer",
                  "description": "English-language answers only."
                },
                "priceLevel": {
                  "type": "string",
                  "description": "Such as `$$`. Optional."
                },
                "status": {
                  "type": "string",
                  "description": "Opening status as displayed. Optional."
                },
                "address": {
                  "type": "string",
                  "description": "Optional."
                },
                "description": {
                  "type": "string",
                  "description": "Optional."
                },
                "url": {
                  "type": "string",
                  "description": "Google's page for the place. Optional."
                }
              }
            }
          },
          "videos": {
            "type": "array",
            "description": "Videos the answer cites.",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "description": "Optional."
                },
                "channel": {
                  "type": "string",
                  "description": "Optional."
                },
                "duration": {
                  "type": "string",
                  "description": "Optional."
                },
                "thumbnail": {
                  "type": "string",
                  "description": "Optional."
                }
              }
            }
          }
        }
      },
      "SearchResponse": {
        "type": "object",
        "description": "Structured search response. Every field besides `results`, `zeroResults` and `paging` is a Google page surface, present only when the page carried it; other engines return `results` and `zeroResults` only. A search that could not be completed is a non-200 status, never an empty 200.",
        "required": [
          "results",
          "zeroResults"
        ],
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            },
            "description": "Ranked web results in engine order. Empty when the page carried no documents."
          },
          "zeroResults": {
            "type": "boolean",
            "description": "`true` when the engine reported that nothing matched (broaden the query; retrying will not help). `false` with empty `results` means a local pack (`places`) or knowledge panel (`entity`) answered instead."
          },
          "paging": {
            "$ref": "#/components/schemas/SearchPaging"
          },
          "spelling": {
            "$ref": "#/components/schemas/SearchSpelling"
          },
          "ads": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchAd"
            }
          },
          "places": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchPlace"
            }
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchProduct"
            },
            "description": "Present only with `searchType: shopping`."
          },
          "entity": {
            "$ref": "#/components/schemas/SearchEntity"
          },
          "relatedSearches": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Query strings from the Related searches block."
          },
          "peopleAlsoAsk": {
            "type": "array",
            "description": "Questions from the People also ask block. No answer text is on the page.",
            "items": {
              "type": "object",
              "properties": {
                "question": {
                  "type": "string"
                }
              }
            }
          },
          "answers": {
            "$ref": "#/components/schemas/SearchAnswers"
          },
          "videos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchVideo"
            }
          },
          "shortVideos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchVideo"
            },
            "description": "Entries of the Short videos block, same shape as `videos`."
          },
          "discussions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchDiscussion"
            }
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchImage"
            }
          },
          "sitelinks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchSitelink"
            }
          },
          "overviews": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchOverview"
            }
          },
          "aiMode": {
            "$ref": "#/components/schemas/AiModeAnswer"
          }
        }
      },
      "SitemapCreateRequest": {
        "type": "object",
        "description": "Request body for POST /sitemap. Any other field is rejected with a 400.",
        "additionalProperties": false,
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Starting URL. The crawl stays on this hostname."
          },
          "maxPages": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "default": 10,
            "description": "Maximum pages to crawl."
          },
          "maxDepth": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 2,
            "description": "Maximum link depth from the starting URL."
          },
          "pathPrefix": {
            "type": "string",
            "description": "Only crawl URLs whose path starts with this prefix."
          },
          "budgetUsd": {
            "type": "number",
            "minimum": 0.0001,
            "description": "Spend cap in USD. Defaults to the quote itself, so every job is capped; the crawl stops with `token_cap_exceeded` before exceeding it."
          },
          "useSitemap": {
            "type": "boolean",
            "default": false,
            "description": "Also seed the crawl from the site's root /sitemap.xml (one extra quoted and billed page)."
          }
        }
      },
      "SitemapJobStatusValue": {
        "type": "string",
        "enum": [
          "awaiting_approval",
          "running",
          "completed",
          "failed",
          "canceled",
          "token_cap_exceeded"
        ],
        "description": "Job status."
      },
      "SitemapQuote": {
        "type": "object",
        "description": "A new job in `awaiting_approval`. Nothing is crawled or charged until approved. Quotes expire after one hour.",
        "required": [
          "jobId",
          "status",
          "estimatedCostUsd",
          "estimatedPages"
        ],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_approval"
            ]
          },
          "estimatedCostUsd": {
            "type": "string",
            "description": "Quoted cost in USD, as a decimal string."
          },
          "estimatedPages": {
            "type": "integer"
          }
        }
      },
      "SitemapJobAck": {
        "type": "object",
        "required": [
          "jobId",
          "status"
        ],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/SitemapJobStatusValue"
          }
        }
      },
      "SitemapJob": {
        "type": "object",
        "description": "Job status. Extra fields depend on `status`: `awaiting_approval`: `estimatedCostUsd`. `running`: `pending`, `processed`. `completed`: `pagesProcessed`, plus `urls` (full results) shortly after completion or `finishedAt`. `failed`: `errorMessage`, `finishedAt`. `canceled` / `token_cap_exceeded`: `pending`, `processed`.",
        "required": [
          "jobId",
          "status"
        ],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/SitemapJobStatusValue"
          },
          "estimatedCostUsd": {
            "type": "string",
            "description": "Quoted cost in USD, as a decimal string."
          },
          "pending": {
            "type": "integer",
            "description": "Queued URLs."
          },
          "processed": {
            "type": "integer",
            "description": "URLs crawled so far."
          },
          "pagesProcessed": {
            "type": "integer"
          },
          "urls": {
            "type": "array",
            "description": "Full results, on a completed job. Use GET /sitemap/{jobId}/urls to page through them."
          },
          "errorMessage": {
            "type": "string",
            "description": "Why the job failed, for example `quote_expired`."
          },
          "finishedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SitemapUrl": {
        "type": "object",
        "description": "One discovered URL.",
        "properties": {
          "url": {
            "type": "string",
            "description": "The discovered URL."
          },
          "statusCode": {
            "type": "integer",
            "description": "HTTP status from fetching the URL; 0 when the fetch failed or the code wasn't retained."
          },
          "discoveredUrls": {
            "type": "integer",
            "description": "New URLs found on this page. Omitted once results are served from durable storage."
          },
          "depth": {
            "type": "integer",
            "description": "Link depth from the starting URL (0 = seed)."
          },
          "isSitemap": {
            "type": "boolean",
            "description": "Whether this URL is a sitemap file rather than a page."
          },
          "error": {
            "type": "string",
            "description": "Present when fetching this URL failed."
          },
          "parentUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "The URL this one was discovered on."
          },
          "sourceType": {
            "type": "string",
            "enum": [
              "seed",
              "sitemap_index",
              "sitemap_urlset",
              "html_link"
            ],
            "description": "How the URL was discovered."
          }
        }
      },
      "SitemapUrlPage": {
        "type": "object",
        "required": [
          "jobId",
          "total",
          "urls"
        ],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "total": {
            "type": "integer",
            "description": "URLs discovered so far. Grows while the crawl runs; check job status to know when the set is complete."
          },
          "urls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SitemapUrl"
            }
          }
        }
      },
      "SitemapJobSummary": {
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "startUrl": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/SitemapJobStatusValue"
          },
          "pagesProcessed": {
            "type": "integer"
          },
          "maxDepth": {
            "type": "integer"
          },
          "maxPages": {
            "type": "integer"
          },
          "estimatedCostUsd": {
            "type": "string",
            "description": "Quoted cost in USD, as a decimal string."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "finishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "errorMessage": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "SitemapJobList": {
        "type": "object",
        "required": [
          "jobs"
        ],
        "properties": {
          "jobs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SitemapJobSummary"
            },
            "description": "Most recent first."
          }
        }
      },
      "SitemapPartialState": {
        "type": "object",
        "description": "Approval handoff incomplete. Retry the approve call.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "partial_state"
            ]
          }
        }
      },
      "ContextStatus": {
        "type": "string",
        "enum": [
          "EMPTY",
          "IN_USE",
          "UPLOADING",
          "READY",
          "FAILED",
          "DELETING"
        ],
        "description": "`EMPTY`: no saved data. `IN_USE`: a saving session is using the profile. `UPLOADING`: a save is finishing. `READY`: saved data is available. `FAILED`: a save failed; earlier saved data remains. `DELETING`: returned only by DELETE."
      },
      "ContextSaveOperation": {
        "type": "object",
        "description": "The most recent save.",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "COMMITTED",
              "FAILED",
              "EXPIRED",
              "SUPERSEDED"
            ],
            "description": "`PENDING`: not finished. `COMMITTED`: succeeded. `FAILED`: failed; read `failureReason`. `EXPIRED`: the session ended before anything was saved. `SUPERSEDED`: another session saved first; not a success."
          },
          "failureReason": {
            "type": [
              "string",
              "null"
            ]
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Context": {
        "type": "object",
        "description": "A saved browser profile. See https://portal.usestring.ai/docs/browser/contexts#the-profile-object",
        "required": [
          "id",
          "name",
          "status",
          "sizeBytes",
          "failureReason",
          "latestSaveOperation",
          "lastSavedAt",
          "lastUsedAt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Opaque UUID used to reconnect (`/wss?contextId=`), inspect, or delete."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional label."
          },
          "status": {
            "$ref": "#/components/schemas/ContextStatus"
          },
          "sizeBytes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Compressed size of the saved profile. Null before the first successful save."
          },
          "failureReason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Most recent save failure, cleared by a successful save."
          },
          "latestSaveOperation": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ContextSaveOperation"
              },
              {
                "type": "null"
              }
            ]
          },
          "lastSavedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Time of the last successful save."
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Last connection, saving or read-only. Reading status does not update it."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateContextRequest": {
        "type": "object",
        "description": "Request body for POST /contexts. Unknown fields return 400.",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Optional label, 1-200 characters after trimming, unique among your live profiles. A duplicate returns 409."
          }
        }
      },
      "ContextList": {
        "type": "object",
        "required": [
          "contexts"
        ],
        "properties": {
          "contexts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Context"
            },
            "description": "Newest first, without deleted profiles."
          }
        }
      },
      "ContextDeleteAccepted": {
        "type": "object",
        "required": [
          "id",
          "status",
          "deleted"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "DELETING"
            ]
          },
          "deleted": {
            "type": "boolean",
            "description": "`false` while stored data is still being removed."
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Validation error: invalid input or an unrecognized field. `issues` lists per-field problems.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid Authorization header.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The key is valid but the account balance can't cover the request.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "DestinationNotEnabled": {
        "description": "Destination domain not enabled for your organization. Email support@usestring.ai to request access.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AccessDeniedResponse"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded. The default limit is 60 requests per second per organization, averaged over a rolling window. Back off and retry with exponential backoff and jitter.",
        "headers": {
          "X-RateLimit-Limit-Rps": {
            "$ref": "#/components/headers/XRateLimitLimitRps"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RateLimitError"
            }
          }
        }
      },
      "InternalError": {
        "description": "Internal server error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "headers": {
      "XRequestId": {
        "description": "Request identifier, useful when contacting support.",
        "schema": {
          "type": "string"
        }
      },
      "XRequestDurationMs": {
        "description": "Total request duration in milliseconds.",
        "schema": {
          "type": "integer"
        }
      },
      "XDataTransferBytes": {
        "description": "Estimated response payload plus header bytes before compression.",
        "schema": {
          "type": "integer"
        }
      },
      "XStatusCode": {
        "description": "The destination's original HTTP status code. Read it for `raw` and `markdown` responses.",
        "schema": {
          "type": "integer"
        }
      },
      "XFinalUrl": {
        "description": "The URL the response was served from after any redirects, or the requested URL if there was no redirect. Same value as `finalUrl` in JSON bodies.",
        "schema": {
          "type": "string"
        }
      },
      "XOriginEtag": {
        "description": "The destination's ETag.",
        "schema": {
          "type": "string"
        }
      },
      "XOriginLastModified": {
        "description": "The destination's Last-Modified value, usable in a later If-Modified-Since request.",
        "schema": {
          "type": "string"
        }
      },
      "XProtocolFallbackUrl": {
        "description": "Present only when an https:// URL could not be reached over TLS and was fetched over plain http:// instead. If you need TLS, treat this header as a failure.",
        "schema": {
          "type": "string"
        }
      },
      "XBilledRequestType": {
        "description": "Billed `/fetch` classification: request strategy and proxy class. `search` marks a Google results URL answered through Search, billed at the search rate.",
        "schema": {
          "type": "string",
          "enum": [
            "request_standard",
            "request_premium",
            "browser_standard",
            "browser_premium",
            "search"
          ]
        }
      },
      "XJsonSchemaApplied": {
        "description": "`jsonSchema` requests only. `true`: the body is the extracted object. `false`: the body is the fallback envelope.",
        "schema": {
          "type": "string",
          "enum": [
            "true",
            "false"
          ]
        }
      },
      "XJsonSchemaFallbackReason": {
        "description": "Present only with `x-json-schema-applied: false`. Same value as the body's `reason`.",
        "schema": {
          "type": "string",
          "enum": [
            "schema_mismatch",
            "unparseable_output",
            "llm_error",
            "body_too_large",
            "empty_body"
          ]
        }
      },
      "XRateLimitLimitRps": {
        "description": "Your allowed average requests per second.",
        "schema": {
          "type": "integer"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying, when present.",
        "schema": {
          "type": "integer"
        }
      }
    }
  }
}
