{
  "swagger": "2.0",
  "info": {
    "description": "The edge API for UpRock services",
    "title": "UpRock Edge API",
    "contact": {},
    "version": "0.9.18"
  },
  "paths": {
    "/compat/exa/contents": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Accepts Exa's contents request and returns Exa's response shape, so exa-py (Exa(api_key, base_url=...)) and exa-js (new Exa(key, baseURL)) work with the base URL set to https://edge.uprock.com/compat/exa (no trailing slash) and an UpRock key, sent as x-api-key (what the SDKs send) or Authorization: Bearer. Each URL is crawled like POST /v1/crawl, concurrently; one crawl event is billed per page delivered.\n\nSupported: urls (or ids), up to 20; text (true/false or {maxCharacters, includeHtmlTags}); livecrawlTimeout (ms, per URL). text is the page as markdown (HTML with includeHtmlTags). Accepted and ignored: highlights, summary, extras, subpages, subpageTarget, livecrawl, maxAgeHours, context, filterEmptyResults, userIdentifier and others. costDollars is not returned.\n\nAs in Exa, per-URL failures are a 200 with the URL's statuses entry set to error (tags CRAWL_HTTP_<code> for a page that answered 4xx/5xx, CRAWL_TIMEOUT, CRAWL_UNKNOWN_ERROR, UNSUPPORTED_URL). Request-level errors use {requestId, error, tag}: 400 INVALID_REQUEST_BODY, 401 INVALID_API_KEY, 403 FEATURE_DISABLED (key lacks crawl.create).",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Exa-compatible contents",
        "parameters": [
          {
            "description": "Exa contents request",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.ExaContentsRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaContentsResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaError"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaError"
            }
          },
          "403": {
            "description": "Forbidden",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaError"
            }
          }
        }
      }
    },
    "/compat/exa/search": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Accepts Exa's search request and returns Exa's response shape, backed by UpRock web research (the same service as POST /v1/research). exa-py (Exa(api_key, base_url=\"https://edge.uprock.com/compat/exa\")) and exa-js (new Exa(key, \"https://edge.uprock.com/compat/exa\")) work with only the base URL and key changed, sent as x-api-key or Authorization: Bearer. Billing is that of web research; this route adds no usage event.\n\nSupported: query, numResults (1-20, default 10), includeDomains, excludeDomains, startPublishedDate, endPublishedDate, userLocation, and contents/text (when requested, each result's text field is populated with the research snippet, truncated to maxCharacters if set). Accepted and ignored: type, category, startCrawlDate, endCrawlDate, includeText, excludeText, useAutoprompt, moderation, livecrawl, highlights, summary, extras and others.\n\nRequest-level errors use {requestId, error, tag, httpStatusCode}: 400 INVALID_REQUEST_BODY, 401 INVALID_API_KEY, 403 FEATURE_DISABLED (key lacks crawl.create), 429 RATE_LIMIT_EXCEEDED, 502/503 INTERNAL_SERVER_ERROR.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Exa-compatible search",
        "parameters": [
          {
            "description": "Exa search request",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.ExaSearchRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaSearchResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaError"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaError"
            }
          },
          "403": {
            "description": "Forbidden",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaError"
            }
          },
          "429": {
            "description": "Too Many Requests",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaError"
            }
          },
          "502": {
            "description": "Bad Gateway",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaError"
            }
          },
          "503": {
            "description": "Service Unavailable",
            "schema": {
              "$ref": "#/definitions/publicapi.ExaError"
            }
          }
        }
      }
    },
    "/compat/firecrawl/v1/scrape": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Accepts Firecrawl's scrape request and returns Firecrawl's response shape, so the official Firecrawl SDKs work with only the base URL and key changed. The crawl and billing are those of POST /v1/crawl: one crawl (or crawl_mobile for mobile: true) event per page delivered.\n\nBase URL: firecrawl-py (api_url=) must use the host root, e.g. https://edge.uprock.com, because it drops any path prefix; it then calls POST /v2/scrape. @mendable/firecrawl-js (apiUrl) can use the host root or https://edge.uprock.com/compat/firecrawl. POST /v1/scrape serves the SDKs' v1 clients.\n\nSupported: url, formats markdown/html/rawHtml/links (strings or {\"type\": ...} objects), onlyMainContent, timeout (ms), mobile, location.country. Other formats (screenshot, json, summary, changeTracking, ...) return 400 UNSUPPORTED_FORMAT. All other request fields (waitFor, headers, actions, includeTags, excludeTags, proxy, maxAge, blockAds, ...) are ignored. html and rawHtml are both the rendered page HTML.\n\nErrors: 400 BAD_REQUEST / UNSUPPORTED_FORMAT, 401 UNAUTHORIZED, 403 FORBIDDEN (key lacks crawl.create), 408 SCRAPE_TIMEOUT, 500 SCRAPE_FAILED. A page that answers with a non-2xx status is still a 200 with metadata.statusCode set, as in Firecrawl.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Firecrawl-compatible scrape",
        "parameters": [
          {
            "description": "Firecrawl scrape request",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlScrapeRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlScrapeResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "403": {
            "description": "Forbidden",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "408": {
            "description": "Request Timeout",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "500": {
            "description": "Internal Server Error",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          }
        }
      }
    },
    "/compat/firecrawl/v2/scrape": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Accepts Firecrawl's scrape request and returns Firecrawl's response shape, so the official Firecrawl SDKs work with only the base URL and key changed. The crawl and billing are those of POST /v1/crawl: one crawl (or crawl_mobile for mobile: true) event per page delivered.\n\nBase URL: firecrawl-py (api_url=) must use the host root, e.g. https://edge.uprock.com, because it drops any path prefix; it then calls POST /v2/scrape. @mendable/firecrawl-js (apiUrl) can use the host root or https://edge.uprock.com/compat/firecrawl. POST /v1/scrape serves the SDKs' v1 clients.\n\nSupported: url, formats markdown/html/rawHtml/links (strings or {\"type\": ...} objects), onlyMainContent, timeout (ms), mobile, location.country. Other formats (screenshot, json, summary, changeTracking, ...) return 400 UNSUPPORTED_FORMAT. All other request fields (waitFor, headers, actions, includeTags, excludeTags, proxy, maxAge, blockAds, ...) are ignored. html and rawHtml are both the rendered page HTML.\n\nErrors: 400 BAD_REQUEST / UNSUPPORTED_FORMAT, 401 UNAUTHORIZED, 403 FORBIDDEN (key lacks crawl.create), 408 SCRAPE_TIMEOUT, 500 SCRAPE_FAILED. A page that answers with a non-2xx status is still a 200 with metadata.statusCode set, as in Firecrawl.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Firecrawl-compatible scrape",
        "parameters": [
          {
            "description": "Firecrawl scrape request",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlScrapeRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlScrapeResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "403": {
            "description": "Forbidden",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "408": {
            "description": "Request Timeout",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "500": {
            "description": "Internal Server Error",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          }
        }
      }
    },
    "/compat/tavily/extract": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Accepts Tavily's extract request and returns Tavily's response shape, so tavily-python (TavilyClient(api_base_url=...)) and @tavily/core (apiBaseURL) work with the base URL set to https://edge.uprock.com/compat/tavily (no trailing slash) and an UpRock key. Each URL is crawled like POST /v1/crawl, concurrently; one crawl event is billed per page delivered. Failed or timed-out URLs are listed in failed_results and not billed.\n\nSupported: urls (string or up to 20), format (markdown | text), timeout (seconds, per URL), include_usage, and api_key in the body as an alternative to Authorization: Bearer. Accepted and ignored: query, chunks_per_source, extract_depth (pages are always rendered with JavaScript), include_favicon. include_images returns an empty images list.\n\nErrors use {\"detail\": {\"error\": \"...\"}}: 400 invalid request, 401 missing or invalid key, 403 key lacks crawl.create.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Tavily-compatible extract",
        "parameters": [
          {
            "description": "Tavily extract request",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyExtractRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyExtractResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyError"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyError"
            }
          },
          "403": {
            "description": "Forbidden",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyError"
            }
          }
        }
      }
    },
    "/compat/tavily/search": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Accepts Tavily's search request and returns Tavily's response shape, backed by UpRock web research (the same service as POST /v1/research). tavily-python (TavilyClient(api_base_url=...)) and @tavily/core (apiBaseURL) work with the base URL set to https://edge.uprock.com/compat/tavily and an UpRock key. Billing is that of web research; this route adds no usage event.\n\nSupported: query, max_results (0-20, default 10), country (Tavily's country names), include_domains, exclude_domains, and api_key in the body as an alternative to Authorization: Bearer. Accepted and ignored: search_depth, topic, time_range, start_date, end_date, include_raw_content (raw_content is null), include_answer (answer is null), include_images (images is empty), chunks_per_source and others. score is rank-based (1 for the first result, decreasing), not a relevance-model score.\n\nErrors use {\"detail\": {\"error\": \"...\"}} with the research service's status: 400 invalid request (including an unknown country), 401 missing or rejected key, 403/429 key limits, 502 when the research service fails, 503 on a node without it.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Tavily-compatible search",
        "parameters": [
          {
            "description": "Tavily search request",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.TavilySearchRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilySearchResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyError"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyError"
            }
          },
          "403": {
            "description": "Forbidden",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyError"
            }
          },
          "429": {
            "description": "Too Many Requests",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyError"
            }
          },
          "502": {
            "description": "Bad Gateway",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyError"
            }
          },
          "503": {
            "description": "Service Unavailable",
            "schema": {
              "$ref": "#/definitions/publicapi.TavilyError"
            }
          }
        }
      }
    },
    "/v1/crawl": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Crawls a URL on a real device with JavaScript rendering, waits for the result server-side, and returns the page as markdown in one call. Speculative retries on other devices start every 7 seconds while the crawl is unfinished, all within timeout_sec.\n\nBilling: exactly one usage event per page, crawl (or crawl_mobile when device_type is mobile). A 200 is billed when it is returned; a 504 is billed once its job_id completes. Requests that fail, or whose job never completes, are not billed, and the internal wait is not billed per poll.\n\nOn 504 CRAWL_TIMEOUT the response carries job_id, which can be polled with GET /crawl/v1/status/{job_id} and fetched with GET /crawl/v1/jobs/{job_id}/download.\n\nEvery response, errors included, carries credits_used (what this request cost at list price: 1 for a crawl, 1.5 for a mobile crawl, 0 for an error), credits_left (the account's credits after this request, from a balance that is cached for up to a minute and itself lags usage by about a minute; null when it is not known, and always for keyless trial requests) and request_id, which is also the X-Request-Id header. A well-formed X-Request-Id sent with the request is kept.\n\nUsage is billed in per-minute batches: each edge location reports a key's crawls of one kind once a minute, and the credits for that batch are rounded half-up to a whole number (at least 1). Mobile crawls can therefore be debited slightly more or less than 1.5 each, and credits_left can drift from the account balance by under 1 credit per batch.\n\nKeyless trial: without an Authorization header, a client IP may make a few crawls per UTC day (10 by default, counted per edge location) to try the API. Trial crawls are free (credits_used 0), run on any device but mobile, are limited to a 30-second timeout_sec, and return no job_id. Every keyless response carries a trial object with the crawls left, the reset time and where to get an API key; when none are left the response is 429 TRIAL_LIMIT_REACHED with Retry-After.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Crawl a page and return it as markdown",
        "parameters": [
          {
            "description": "Crawl parameters",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.CrawlRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Crawled page",
            "schema": {
              "$ref": "#/definitions/publicapi.CrawlResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "400": {
            "description": "INVALID_REQUEST",
            "schema": {
              "$ref": "#/definitions/publicapi.ErrorResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "401": {
            "description": "UNAUTHORIZED: missing or invalid API key",
            "schema": {
              "$ref": "#/definitions/publicapi.ErrorResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "402": {
            "description": "BILLING_PAYMENT_REQUIRED: the API key's org is out of credits; add credits or enable PAYG. BILLING_API_KEY_SPEND_CAP_REACHED: the API key reached its monthly spend cap; the message says when it resets (the body has code, error and request_id, without the credits fields)",
            "schema": {
              "$ref": "#/definitions/publicapi.ErrorResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "403": {
            "description": "FORBIDDEN: the API key lacks the crawl.create permission",
            "schema": {
              "$ref": "#/definitions/publicapi.ErrorResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "429": {
            "description": "TRIAL_LIMIT_REACHED: no keyless trial crawls left today",
            "schema": {
              "$ref": "#/definitions/publicapi.ErrorResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR",
            "schema": {
              "$ref": "#/definitions/publicapi.ErrorResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "502": {
            "description": "CRAWL_FAILED: every attempt failed",
            "schema": {
              "$ref": "#/definitions/publicapi.ErrorResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "504": {
            "description": "CRAWL_TIMEOUT: not finished within timeout_sec; includes job_id",
            "schema": {
              "$ref": "#/definitions/publicapi.ErrorResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          }
        }
      }
    },
    "/v1/feedback": {
      "post": {
        "description": "Allows an agent or developer (with an API key or keyless from a trial request) to report a blocked, empty, wrong_content, timeout, or other bad result by its request_id. Always free (0 credits).",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Report a bad crawl, research, or VSS result by request_id",
        "parameters": [
          {
            "description": "Feedback payload",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.FeedbackRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Feedback recorded",
            "schema": {
              "$ref": "#/definitions/publicapi.FeedbackResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request"
              }
            }
          },
          "400": {
            "description": "INVALID_REQUEST: missing request_id or invalid reason",
            "schema": {
              "$ref": "#/definitions/publicapi.ErrorResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request"
              }
            }
          }
        }
      }
    },
    "/v1/research": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Searches the web from real devices in several countries and returns deduplicated results, the same as the MCP web_research tool, over plain HTTP. Send the API key as Authorization: Bearer <key>; there is no fallback to a server-side key.\n\nThe request is strict JSON: unknown fields (e.g. countries instead of suggested_countries) are a 400.\n\nBilling: the research service bills the underlying search crawls on the caller's key (or one research_cached event for a cached answer). Every response includes credits_used, credits_left and request_id (also returned as X-Request-Id), and error responses include next_step.\n\nErrors are {\"error\": {\"code\", \"message\"}, \"credits_used\": 0, \"credits_left\": ..., \"request_id\": \"...\", \"next_step\": \"...\"}: 400 invalid_request, 401 missing_api_key or a rejected key (403/429 pass through, e.g. a daily cap), 502 upstream_unavailable, 503 research_unavailable on a node without the research service.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Web research",
        "parameters": [
          {
            "description": "Research parameters",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.ResearchRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/publicapi.ResearchResponse"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "$ref": "#/definitions/publicapi.ResearchError"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/publicapi.ResearchError"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "schema": {
              "$ref": "#/definitions/publicapi.ResearchError"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "schema": {
              "$ref": "#/definitions/publicapi.ResearchError"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "502": {
            "description": "Bad Gateway",
            "schema": {
              "$ref": "#/definitions/publicapi.ResearchError"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "schema": {
              "$ref": "#/definitions/publicapi.ResearchError"
            },
            "headers": {
              "X-Request-Id": {
                "type": "string",
                "description": "ID of this request, same as request_id"
              }
            }
          }
        }
      }
    },
    "/v1/scrape": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Accepts Firecrawl's scrape request and returns Firecrawl's response shape, so the official Firecrawl SDKs work with only the base URL and key changed. The crawl and billing are those of POST /v1/crawl: one crawl (or crawl_mobile for mobile: true) event per page delivered.\n\nBase URL: firecrawl-py (api_url=) must use the host root, e.g. https://edge.uprock.com, because it drops any path prefix; it then calls POST /v2/scrape. @mendable/firecrawl-js (apiUrl) can use the host root or https://edge.uprock.com/compat/firecrawl. POST /v1/scrape serves the SDKs' v1 clients.\n\nSupported: url, formats markdown/html/rawHtml/links (strings or {\"type\": ...} objects), onlyMainContent, timeout (ms), mobile, location.country. Other formats (screenshot, json, summary, changeTracking, ...) return 400 UNSUPPORTED_FORMAT. All other request fields (waitFor, headers, actions, includeTags, excludeTags, proxy, maxAge, blockAds, ...) are ignored. html and rawHtml are both the rendered page HTML.\n\nErrors: 400 BAD_REQUEST / UNSUPPORTED_FORMAT, 401 UNAUTHORIZED, 403 FORBIDDEN (key lacks crawl.create), 408 SCRAPE_TIMEOUT, 500 SCRAPE_FAILED. A page that answers with a non-2xx status is still a 200 with metadata.statusCode set, as in Firecrawl.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Firecrawl-compatible scrape",
        "parameters": [
          {
            "description": "Firecrawl scrape request",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlScrapeRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlScrapeResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "403": {
            "description": "Forbidden",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "408": {
            "description": "Request Timeout",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "500": {
            "description": "Internal Server Error",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          }
        }
      }
    },
    "/v1/stats": {
      "get": {
        "description": "Aggregate statistics for the UpRock network. No API key is required.\n\n- nodes_online: devices that checked in within the last online_window_minutes (15), in total, by ISO 3166-1 alpha-2 country code and by device type. Countries with fewer than 5 online devices are omitted from by_country (still counted in total).\n- crawl_24h: crawl jobs that reached a terminal status in the last 24 hours, the share that completed, and the p50/p95 submit-to-completion latency of completed jobs.\n\nAggregates only: no device, IP or user data. Numbers are refreshed every 60 seconds network-wide; updated_at says when. Rate limited to 60 requests per minute per IP. Browser access is allowed from uprock.ai.",
        "produces": [
          "application/json"
        ],
        "tags": [
          "stats"
        ],
        "summary": "Public network statistics",
        "responses": {
          "200": {
            "description": "Network statistics",
            "schema": {
              "$ref": "#/definitions/netstats.Snapshot"
            }
          },
          "429": {
            "description": "RATE_LIMITED",
            "schema": {
              "$ref": "#/definitions/netstats.ErrorResponse"
            }
          },
          "503": {
            "description": "STATS_UNAVAILABLE: no statistics computed yet",
            "schema": {
              "$ref": "#/definitions/netstats.ErrorResponse"
            }
          }
        }
      }
    },
    "/v1/verify": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Loads a URL on real devices in several regions, with screenshots and performance metrics, to check that a deploy works everywhere. Same request and response as the deprecated POST /crawl/v1/sweep/new.\n\nExample request:\n```json\n{\n\"url\": \"https://example.com\",\n\"regions\": [\"NA\", \"EU\", \"APAC\"],\n\"tries_per_region\": 3,\n\"timeout_sec\": 60,\n\"device_type\": \"mobile\"\n}\n```\n\nRegions: NA (North America), EU (Europe), APAC (Asia-Pacific), LATAM (Latin America), MEA (Middle East & Africa).\n\nPoll GET /v1/verify/{sweep_id}/status until done, then fetch the results with GET /v1/verify/{sweep_id}. Requires the sweep.create permission; billed as sweep.create, one per requested job.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "verify"
        ],
        "summary": "Verify a URL from several regions",
        "parameters": [
          {
            "description": "Verify parameters",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/crawl.SweepCreateRequest"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Verification created",
            "schema": {
              "$ref": "#/definitions/crawl.SweepCreateResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "type": "string"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "type": "string"
            }
          },
          "402": {
            "description": "BILLING_PAYMENT_REQUIRED or BILLING_API_KEY_SPEND_CAP_REACHED",
            "schema": {
              "type": "string"
            }
          },
          "500": {
            "description": "Internal Server Error",
            "schema": {
              "type": "string"
            }
          }
        }
      }
    },
    "/v1/verify/{sweep_id}": {
      "get": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Returns the status of every job of a verification, by region; completed jobs include performance metrics and base64-encoded screenshots. The response always has exactly the number of slots requested: unfilled slots are pending while jobs still run (their job_id is the all-zero UUID), or failures when no more jobs can succeed. Same response as the deprecated GET /crawl/v1/sweep/{sweep_id}. Requires the sweep.view permission.",
        "produces": [
          "application/json"
        ],
        "tags": [
          "verify"
        ],
        "summary": "Get the results of a verification",
        "parameters": [
          {
            "type": "string",
            "description": "The verification (sweep) ID",
            "name": "sweep_id",
            "in": "path",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Verification status and results",
            "schema": {
              "$ref": "#/definitions/crawl.SweepStatusResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "type": "string"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "type": "string"
            }
          },
          "404": {
            "description": "Verification not found",
            "schema": {
              "type": "string"
            }
          },
          "500": {
            "description": "Internal Server Error",
            "schema": {
              "type": "string"
            }
          }
        }
      }
    },
    "/v1/verify/{sweep_id}/status": {
      "get": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Returns only the overall status and a done flag, without results; cheap enough to poll. Fetch the results with GET /v1/verify/{sweep_id} once done is true. Same response as the deprecated GET /crawl/v1/sweep/{sweep_id}/status. Requires the sweep.view permission.",
        "produces": [
          "application/json"
        ],
        "tags": [
          "verify"
        ],
        "summary": "Poll a verification (lightweight)",
        "parameters": [
          {
            "type": "string",
            "description": "The verification (sweep) ID",
            "name": "sweep_id",
            "in": "path",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Verification light status",
            "schema": {
              "$ref": "#/definitions/crawl.SweepLightStatusResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "type": "string"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "type": "string"
            }
          },
          "404": {
            "description": "Verification not found",
            "schema": {
              "type": "string"
            }
          }
        }
      }
    },
    "/v2/scrape": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Accepts Firecrawl's scrape request and returns Firecrawl's response shape, so the official Firecrawl SDKs work with only the base URL and key changed. The crawl and billing are those of POST /v1/crawl: one crawl (or crawl_mobile for mobile: true) event per page delivered.\n\nBase URL: firecrawl-py (api_url=) must use the host root, e.g. https://edge.uprock.com, because it drops any path prefix; it then calls POST /v2/scrape. @mendable/firecrawl-js (apiUrl) can use the host root or https://edge.uprock.com/compat/firecrawl. POST /v1/scrape serves the SDKs' v1 clients.\n\nSupported: url, formats markdown/html/rawHtml/links (strings or {\"type\": ...} objects), onlyMainContent, timeout (ms), mobile, location.country. Other formats (screenshot, json, summary, changeTracking, ...) return 400 UNSUPPORTED_FORMAT. All other request fields (waitFor, headers, actions, includeTags, excludeTags, proxy, maxAge, blockAds, ...) are ignored. html and rawHtml are both the rendered page HTML.\n\nErrors: 400 BAD_REQUEST / UNSUPPORTED_FORMAT, 401 UNAUTHORIZED, 403 FORBIDDEN (key lacks crawl.create), 408 SCRAPE_TIMEOUT, 500 SCRAPE_FAILED. A page that answers with a non-2xx status is still a 200 with metadata.statusCode set, as in Firecrawl.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "crawl"
        ],
        "summary": "Firecrawl-compatible scrape",
        "parameters": [
          {
            "description": "Firecrawl scrape request",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlScrapeRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlScrapeResponse"
            }
          },
          "400": {
            "description": "Bad Request",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "403": {
            "description": "Forbidden",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "408": {
            "description": "Request Timeout",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          },
          "500": {
            "description": "Internal Server Error",
            "schema": {
              "$ref": "#/definitions/publicapi.FirecrawlError"
            }
          }
        }
      }
    },
    "/vss/v1/frames": {
      "post": {
        "description": "Returns the query-relevant keyframe images (\"film strip\") for the\nsearch result at a given rank position. Internally re-runs the search\nquery to resolve the position to a moment id, then retrieves and\nre-ranks that moment's stored keyframes by query relevance.\n\nAuth + tenancy are identical to POST /vss/v1/search: an API key\nholding vss.search (customer-scoped + public catalog), a rocket JWT\n(public catalog only), or public_only=true to force public scope.\n\nThe response is multipart/mixed. Part 0 is a JSON manifest:\n{moment_id, frames:[{score, ts, content_id}]}. Subsequent parts are\nimage/jpeg carrying raw JPEG bytes, each with a Content-ID header\nmatching the content_id in the manifest (format: \"<id.n@frames.uprock.com>\").",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "multipart/mixed"
        ],
        "tags": [
          "vss"
        ],
        "summary": "Fetch keyframe film-strip for a search result by position",
        "parameters": [
          {
            "description": "Frames request: query (string, required), position (int32, 0-based rank of the result to get frames for; default 0), limit (int32, max keyframes to return; 0 = service default ~4), public_only (bool)",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "multipart/mixed: part 0 = JSON manifest {moment_id, frames:[{score,ts,content_id}]}, remaining parts = image/jpeg keyframes matched by Content-ID",
            "schema": {
              "type": "string"
            }
          },
          "400": {
            "description": "Bad Request (missing query, invalid body, or invalid position)",
            "schema": {
              "type": "object"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "type": "object"
            }
          },
          "404": {
            "description": "No result at the given position",
            "schema": {
              "type": "object"
            }
          },
          "502": {
            "description": "Search or frames service error",
            "schema": {
              "type": "object"
            }
          },
          "503": {
            "description": "No search capacity available",
            "schema": {
              "type": "object"
            }
          }
        }
      }
    },
    "/vss/v1/moments": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Resolves the URL to the canonical video id and streams text-moments\n(interleaved transcript + visual descriptions) as newline-delimited JSON,\none moment object per line, in completion order (re-order by t0_s for\ndisplay). Each line carries video_id, canonical_url, moment_key, index,\ntotal_moments, t0_s, t1_s, text, next_offset_s, has_more, status, and error.\nAuth: an API key holding vss.moment (the customer's tenancy scope), or a\nrocket JWT (public catalog only). When the video has no in-scope moments\nthe service returns NOT_FOUND and the edge does ingest-on-miss: 404 if the\nvideo is ingested but out of your scope; 202 {\"status\":\"indexing\",...} when\nan ingest is in flight or was just submitted (poll the crawl lifecycle\nendpoint); 404 {\"status\":\"not_indexed\"} otherwise.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/x-ndjson"
        ],
        "tags": [
          "vss"
        ],
        "summary": "Fetch on-demand text-moments for a video by URL (NDJSON stream)",
        "parameters": [
          {
            "description": "Moment request: url (required), start_s, end_s, max_moments, resolution, two_pass, force_regen, recipe_version, public_only",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/vss.momentRequestBody"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "NDJSON stream of moment objects (one per line)",
            "schema": {
              "$ref": "#/definitions/vss.momentLine"
            }
          },
          "202": {
            "description": "Video not indexed in scope; ingest submitted or already in flight",
            "schema": {
              "$ref": "#/definitions/vss.momentMissResponse"
            }
          },
          "400": {
            "description": "Bad Request (missing url / invalid body)",
            "schema": {
              "$ref": "#/definitions/vss.httpErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/vss.httpErrorResponse"
            }
          },
          "403": {
            "description": "Forbidden \u2014 the API key lacks the vss.moment permission",
            "schema": {
              "$ref": "#/definitions/vss.httpErrorResponse"
            }
          },
          "404": {
            "description": "Video not available in your scope / not indexed",
            "schema": {
              "$ref": "#/definitions/vss.momentMissResponse"
            }
          },
          "502": {
            "description": "Moment service error",
            "schema": {
              "$ref": "#/definitions/vss.httpErrorResponse"
            }
          },
          "503": {
            "description": "No moment capacity available",
            "schema": {
              "$ref": "#/definitions/vss.httpErrorResponse"
            }
          }
        }
      }
    },
    "/vss/v1/search": {
      "post": {
        "security": [
          {
            "Bearer": []
          }
        ],
        "description": "Searches the VSS (Video Semantic Search) index and returns ranked\nmoment results. Accepts either a JSON body or multipart/form-data\n(required when sending an image for image-query).\n\nAuth: an API key holding vss.search (scopes to the key's OrgID\ntenancy, including that customer's private index plus the public\ncatalog), or a rocket JWT (public catalog only). Setting public_only\nforces a public-catalog-only result even when a valid API key is\npresented. Always authenticates \u2014 unauthenticated requests are\nrejected with 401; a valid API key lacking vss.search is rejected\nwith 403.\n\nThe mode field controls result deduplication: omit or set \"\" for the\ndefault per-moment ranking (multiple moments from the same video may\nappear), or set \"unique\" to return at most one best-scoring moment per\nvideo (useful for a \"find this video\" UX).\n\nEach result includes: id, video_id, t0, t1, score, title, transcript,\noccurrence_count, origin_url, source, published_at.",
        "consumes": [
          "application/json",
          "multipart/form-data"
        ],
        "produces": [
          "application/json"
        ],
        "tags": [
          "vss"
        ],
        "summary": "Search video moments by text or image query",
        "parameters": [
          {
            "description": "Search request. JSON fields: query (string), tags (map of string to string), limit (int32, default backend-defined), public_only (bool), published_after (int64 unix sec, inclusive), published_before (int64 unix sec, inclusive), order_by_recency (bool, resort results newest-first), mode (string; empty for the per-moment default, or unique for one best result per video). For image queries use multipart/form-data with an additional image part.",
            "name": "request",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/vss.searchRequestBody"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results: {results: [{id, video_id, t0, t1, score, title, transcript, occurrence_count, origin_url, source, published_at}]}",
            "schema": {
              "$ref": "#/definitions/vss.searchResponseBody"
            }
          },
          "400": {
            "description": "Bad Request (invalid body)",
            "schema": {
              "$ref": "#/definitions/vss.httpErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "schema": {
              "$ref": "#/definitions/vss.httpErrorResponse"
            }
          },
          "403": {
            "description": "Forbidden \u2014 the API key lacks the vss.search permission",
            "schema": {
              "$ref": "#/definitions/vss.httpErrorResponse"
            }
          },
          "502": {
            "description": "Search service error",
            "schema": {
              "$ref": "#/definitions/vss.httpErrorResponse"
            }
          },
          "503": {
            "description": "No search capacity available",
            "schema": {
              "$ref": "#/definitions/vss.httpErrorResponse"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "crawl.PerformanceMetricsSummary": {
      "type": "object",
      "properties": {
        "cls": {
          "description": "Cumulative Layout Shift score (0-1+, lower is better)\nexample: 0.05",
          "type": "number"
        },
        "dom_content_loaded": {
          "description": "DOMContentLoaded time in milliseconds\nexample: 850.2",
          "type": "number"
        },
        "fcp": {
          "description": "First Contentful Paint in milliseconds\nexample: 320.5",
          "type": "number"
        },
        "lcp": {
          "description": "Largest Contentful Paint in milliseconds\nexample: 780.3",
          "type": "number"
        },
        "protocol": {
          "description": "HTTP protocol version\nexample: h2",
          "type": "string"
        },
        "total_load_time": {
          "description": "Total page load time in milliseconds\nexample: 1250.8",
          "type": "number"
        },
        "transfer_size": {
          "description": "Total bytes transferred\nexample: 524288",
          "type": "integer"
        },
        "ttfb": {
          "description": "Time to first byte in milliseconds\nexample: 150.5",
          "type": "number"
        }
      }
    },
    "crawl.RegionResult": {
      "type": "object",
      "properties": {
        "jobs": {
          "description": "Individual job results for this region",
          "type": "array",
          "items": {
            "$ref": "#/definitions/crawl.SweepJobResult"
          }
        },
        "region": {
          "description": "Region code\nexample: NA",
          "type": "string"
        },
        "status": {
          "description": "Status of this region's jobs\nenum: pending,in_progress,completed,partial,failed\nexample: completed",
          "type": "string"
        }
      }
    },
    "crawl.SweepCreateRequest": {
      "type": "object",
      "properties": {
        "device_type": {
          "description": "Device type to use for crawling\nenum: mobile,desktop\ndefault: mobile\nexample: mobile",
          "type": "string"
        },
        "regions": {
          "description": "List of region codes to crawl from\nrequired: true\nexample: [\"NA\", \"EU\", \"APAC\"]",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "timeout_sec": {
          "description": "Request timeout in seconds for each job\nminimum: 1\nmaximum: 120\ndefault: 60\nexample: 60",
          "type": "integer"
        },
        "tries_per_region": {
          "description": "Number of crawl attempts per region\nminimum: 1\nmaximum: 10\ndefault: 3\nexample: 3",
          "type": "integer"
        },
        "url": {
          "description": "URL to crawl from multiple regions\nrequired: true\nexample: https://example.com",
          "type": "string"
        }
      }
    },
    "crawl.SweepCreateResponse": {
      "type": "object",
      "properties": {
        "created_at": {
          "description": "When the sweep was created\nexample: 2024-01-15T09:30:00Z",
          "type": "string"
        },
        "job_ids": {
          "description": "Placeholder job IDs for virtual slots (initially all zeros).\nReal job IDs are populated when checking sweep status as jobs complete.\nexample: [\"00000000-0000-0000-0000-000000000000\", \"00000000-0000-0000-0000-000000000000\"]",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "regions": {
          "description": "Regions included in the sweep\nexample: [\"NA\", \"EU\", \"APAC\"]",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "sweep_id": {
          "description": "The unique ID of the created sweep\nexample: 550e8400-e29b-41d4-a716-446655440000",
          "type": "string"
        },
        "total_jobs": {
          "description": "Total number of virtual slots (regions \u00d7 tries_per_region).\nThis matches what the client requested, not the actual number of jobs created internally.\nexample: 9",
          "type": "integer"
        },
        "url": {
          "description": "The URL being crawled\nexample: https://example.com",
          "type": "string"
        }
      }
    },
    "crawl.SweepJobResult": {
      "type": "object",
      "properties": {
        "country": {
          "description": "Country code where the job ran (if completed)\nexample: US",
          "type": "string"
        },
        "data_error": {
          "description": "Error message when metrics/screenshot retrieval failed for a completed job\nThis distinguishes \"data not yet available\" from \"data retrieval failed\"\nexample: failed to read storage",
          "type": "string"
        },
        "download_url": {
          "description": "Download URL for the early result data (when partial_available is true)\nexample: /crawl/v1/jobs/550e8400-e29b-41d4-a716-446655440001/download",
          "type": "string"
        },
        "error_message": {
          "description": "Error message (if failed)\nexample: Request timed out after 60 seconds",
          "type": "string"
        },
        "error_type": {
          "description": "Error type (if failed)\nexample: timeout",
          "type": "string"
        },
        "job_id": {
          "description": "The job ID\nexample: 550e8400-e29b-41d4-a716-446655440001",
          "type": "string"
        },
        "metrics": {
          "description": "Performance metrics (if completed successfully)",
          "allOf": [
            {
              "$ref": "#/definitions/crawl.PerformanceMetricsSummary"
            }
          ]
        },
        "partial_available": {
          "description": "Whether partial data (HTML/DOM) is available for download\nThis is true when the job is in PARTIAL state or completed with partial_only",
          "type": "boolean"
        },
        "screenshot": {
          "description": "Base64-encoded screenshot (if completed successfully and screenshot available)",
          "type": "string"
        },
        "screenshot_status": {
          "description": "Screenshot status for two-phase sweeps (per winner slot)\nenum: pending,ready,unavailable\nexample: ready",
          "type": "string"
        },
        "status": {
          "description": "Current status of the job\nenum: pending,assigned,partial,completed,failed,timeout\nexample: completed",
          "type": "string"
        }
      }
    },
    "crawl.SweepLightStatusResponse": {
      "type": "object",
      "properties": {
        "done": {
          "description": "True when status is terminal (completed or failed)\nexample: false",
          "type": "boolean"
        },
        "status": {
          "description": "Overall status of the sweep based on virtual slot counts\nenum: pending,in_progress,completed,failed\nexample: in_progress",
          "type": "string"
        },
        "sweep_id": {
          "description": "The sweep ID\nexample: 550e8400-e29b-41d4-a716-446655440000",
          "type": "string"
        }
      }
    },
    "crawl.SweepStatusResponse": {
      "type": "object",
      "properties": {
        "completed_jobs": {
          "description": "Number of virtual slots filled with successful completions.\nCapped at the requested tries_per_region per region.\nexample: 6",
          "type": "integer"
        },
        "created_at": {
          "description": "When the sweep was created\nexample: 2024-01-15T09:30:00Z",
          "type": "string"
        },
        "failed_jobs": {
          "description": "Number of virtual slots showing failures (includes timeout, session_gone, archived).\nOnly counted when no pending jobs remain to fill the slot.\nexample: 1",
          "type": "integer"
        },
        "results": {
          "description": "Results organized by region",
          "type": "object",
          "additionalProperties": {
            "$ref": "#/definitions/crawl.RegionResult"
          }
        },
        "results_ready_at": {
          "description": "ResultsReadyAt is the timestamp when all P1 phases resolved (two-phase sweeps only).\nNil until the sweep reaches \"ready\" status.\nexample: 2024-01-15T09:30:15Z",
          "type": "string"
        },
        "screenshots_complete": {
          "description": "ScreenshotsComplete indicates all winner screenshots are terminal (two-phase sweeps only).\nexample: true",
          "type": "boolean"
        },
        "status": {
          "description": "Overall status of the sweep based on virtual slot counts\nenum: pending,in_progress,completed,failed\nexample: in_progress",
          "type": "string"
        },
        "sweep_id": {
          "description": "The sweep ID\nexample: 550e8400-e29b-41d4-a716-446655440000",
          "type": "string"
        },
        "total_jobs": {
          "description": "Total number of virtual slots (regions \u00d7 tries_per_region).\nThis matches what the client requested, not actual jobs created.\nexample: 9",
          "type": "integer"
        },
        "url": {
          "description": "The URL being crawled\nexample: https://example.com",
          "type": "string"
        }
      }
    },
    "netstats.Crawl24h": {
      "type": "object",
      "properties": {
        "jobs": {
          "description": "Jobs that reached a terminal status (completed, failed or timed out)",
          "type": "integer",
          "example": 100000
        },
        "latency_ms": {
          "$ref": "#/definitions/netstats.Latency"
        },
        "success_rate": {
          "description": "Completed jobs / jobs; null when there were no jobs",
          "type": "number",
          "example": 0.97
        }
      }
    },
    "netstats.ErrorResponse": {
      "type": "object",
      "properties": {
        "code": {
          "description": "Stable machine-readable error code",
          "type": "string",
          "enum": [
            "STATS_UNAVAILABLE",
            "RATE_LIMITED",
            "NOT_FOUND",
            "METHOD_NOT_ALLOWED"
          ],
          "example": "RATE_LIMITED"
        },
        "error": {
          "description": "Human-readable message",
          "type": "string"
        }
      }
    },
    "netstats.Latency": {
      "type": "object",
      "properties": {
        "p50": {
          "description": "Median latency in milliseconds; null when unavailable",
          "type": "integer",
          "example": 2100
        },
        "p95": {
          "description": "95th percentile latency in milliseconds; null when unavailable",
          "type": "integer",
          "example": 7800
        }
      }
    },
    "netstats.NodesOnline": {
      "type": "object",
      "properties": {
        "by_country": {
          "description": "Online devices by ISO 3166-1 alpha-2 country code; countries with fewer than 5 are omitted",
          "type": "object",
          "additionalProperties": {
            "type": "integer"
          }
        },
        "by_device_type": {
          "description": "Online devices by device type (mobile, desktop, ...)",
          "type": "object",
          "additionalProperties": {
            "type": "integer"
          }
        },
        "total": {
          "description": "All online devices, including those in countries hidden from by_country",
          "type": "integer",
          "example": 1234
        }
      }
    },
    "netstats.Snapshot": {
      "type": "object",
      "properties": {
        "crawl_24h": {
          "$ref": "#/definitions/netstats.Crawl24h"
        },
        "nodes_online": {
          "$ref": "#/definitions/netstats.NodesOnline"
        },
        "online_window_minutes": {
          "description": "A device counts as online if it checked in within this many minutes",
          "type": "integer",
          "example": 15
        },
        "updated_at": {
          "description": "When these numbers were computed",
          "type": "string"
        }
      }
    },
    "publicapi.CrawlRequest": {
      "type": "object",
      "required": [
        "url"
      ],
      "properties": {
        "country": {
          "description": "Where to crawl from: an ISO 3166-1 alpha-2 code or a meta-region (NA, EU, APAC, LATAM, MEA). Omit for any location.",
          "type": "string",
          "example": "US"
        },
        "device_type": {
          "description": "Device to crawl with. Omit for any device. Mobile crawls are billed as crawl_mobile.",
          "type": "string",
          "enum": [
            "mobile",
            "desktop"
          ],
          "example": "desktop"
        },
        "format": {
          "description": "Output format: markdown, or html to also return the rendered HTML",
          "type": "string",
          "default": "markdown",
          "enum": [
            "markdown",
            "html"
          ]
        },
        "timeout_sec": {
          "description": "Overall time budget in seconds, including retries",
          "type": "integer",
          "default": 60,
          "maximum": 100,
          "minimum": 10,
          "example": 30
        },
        "url": {
          "description": "URL of the page to crawl (http or https)",
          "type": "string",
          "example": "https://example.com"
        }
      }
    },
    "publicapi.CrawlResponse": {
      "type": "object",
      "properties": {
        "credits_left": {
          "description": "Credits left on the account after this request; null when not known. Read from a balance cached for up to a minute that itself lags usage by about a minute, so it is an estimate.",
          "type": "number",
          "example": 4999
        },
        "credits_used": {
          "description": "Credits this request used, at list price: 1 for a crawl, 1.5 for a mobile crawl. Billing rounds each per-minute batch of a key's usage half-up, so the amount debited can differ by under 1 credit per batch.",
          "type": "number",
          "example": 1
        },
        "html": {
          "description": "Rendered HTML; only present when format is html",
          "type": "string"
        },
        "job_id": {
          "description": "Crawl job ID; usable with the async /crawl/v1 API. Absent for keyless trial crawls.",
          "type": "string",
          "example": "550e8400-e29b-41d4-a716-446655440000"
        },
        "markdown": {
          "description": "Page content as markdown",
          "type": "string"
        },
        "request_id": {
          "description": "ID of this request, also returned as the X-Request-Id header",
          "type": "string",
          "example": "req_9f1c2a7e4b8d4c0fa1e2b3c4d5e6f708"
        },
        "status_code": {
          "description": "HTTP status code of the page",
          "type": "integer",
          "example": 200
        },
        "title": {
          "description": "Document title",
          "type": "string",
          "example": "Example Domain"
        },
        "trial": {
          "description": "Keyless trial state; only present on requests without an API key",
          "allOf": [
            {
              "$ref": "#/definitions/publicapi.TrialInfo"
            }
          ]
        },
        "url": {
          "description": "The crawled URL",
          "type": "string",
          "example": "https://example.com"
        }
      }
    },
    "publicapi.ErrorResponse": {
      "type": "object",
      "properties": {
        "code": {
          "description": "Stable machine-readable error code",
          "type": "string",
          "enum": [
            "INVALID_REQUEST",
            "UNAUTHORIZED",
            "BILLING_PAYMENT_REQUIRED",
            "BILLING_API_KEY_SPEND_CAP_REACHED",
            "FORBIDDEN",
            "NOT_FOUND",
            "METHOD_NOT_ALLOWED",
            "TRIAL_LIMIT_REACHED",
            "CRAWL_FAILED",
            "CRAWL_TIMEOUT",
            "INTERNAL_ERROR"
          ],
          "example": "UNAUTHORIZED"
        },
        "credits_left": {
          "description": "Credits left on the account; null when not known",
          "type": "number"
        },
        "credits_used": {
          "description": "Credits this request used: always 0 for an error. A CRAWL_TIMEOUT job that completes later is billed then.",
          "type": "number",
          "example": 0
        },
        "error": {
          "description": "Human-readable message",
          "type": "string",
          "example": "invalid API key"
        },
        "job_id": {
          "description": "On CRAWL_TIMEOUT (and CRAWL_FAILED): the crawl job, pollable via GET /crawl/v1/status/{job_id}",
          "type": "string"
        },
        "next_step": {
          "description": "Actionable recovery hint for the calling agent or developer",
          "type": "string",
          "example": "Get an API key at https://uprock.ai/auth.md or run: uprock auth login --setup-ai"
        },
        "request_id": {
          "description": "ID of this request, also returned as the X-Request-Id header",
          "type": "string",
          "example": "req_9f1c2a7e4b8d4c0fa1e2b3c4d5e6f708"
        },
        "trial": {
          "description": "Keyless trial state; only present on requests without an API key",
          "allOf": [
            {
              "$ref": "#/definitions/publicapi.TrialInfo"
            }
          ]
        }
      }
    },
    "publicapi.ExaContentResult": {
      "type": "object",
      "properties": {
        "id": {
          "description": "The requested URL",
          "type": "string"
        },
        "text": {
          "description": "Page content as markdown, or HTML when text.includeHtmlTags is true",
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "url": {
          "type": "string"
        }
      }
    },
    "publicapi.ExaContentStatus": {
      "type": "object",
      "properties": {
        "error": {
          "$ref": "#/definitions/publicapi.ExaStatusError"
        },
        "id": {
          "type": "string"
        },
        "source": {
          "type": "string",
          "enum": [
            "crawled"
          ]
        },
        "status": {
          "type": "string",
          "enum": [
            "success",
            "error"
          ]
        }
      }
    },
    "publicapi.ExaContentsRequest": {
      "type": "object",
      "properties": {
        "ids": {
          "description": "Same as urls (Exa's older name); ignored when urls is set",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "livecrawlTimeout": {
          "description": "Per-URL crawl budget in milliseconds (default 30000), clamped to 10000-90000",
          "type": "integer",
          "example": 30000
        },
        "text": {
          "description": "true, false, or {\"maxCharacters\": n, \"includeHtmlTags\": bool}. Default: text included",
          "type": "object"
        },
        "urls": {
          "description": "URLs to crawl (up to 20)",
          "type": "array",
          "items": {
            "type": "string"
          },
          "example": [
            "https://example.com"
          ]
        }
      }
    },
    "publicapi.ExaContentsResponse": {
      "type": "object",
      "properties": {
        "requestId": {
          "type": "string"
        },
        "results": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/publicapi.ExaContentResult"
          }
        },
        "statuses": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/publicapi.ExaContentStatus"
          }
        }
      }
    },
    "publicapi.ExaError": {
      "type": "object",
      "properties": {
        "error": {
          "type": "string"
        },
        "httpStatusCode": {
          "type": "integer"
        },
        "requestId": {
          "type": "string"
        },
        "tag": {
          "type": "string",
          "enum": [
            "INVALID_REQUEST_BODY",
            "INVALID_API_KEY",
            "FEATURE_DISABLED",
            "RATE_LIMIT_EXCEEDED",
            "INTERNAL_SERVER_ERROR"
          ]
        }
      }
    },
    "publicapi.ExaSearchRequest": {
      "type": "object",
      "required": [
        "query"
      ],
      "properties": {
        "contents": {
          "description": "Optional contents options (boolean or {\"text\": true | {\"maxCharacters\": n}})",
          "type": "object"
        },
        "endPublishedDate": {
          "description": "Optional upper bound on publishedDate (RFC3339 or YYYY-MM-DD)",
          "type": "string",
          "example": "2026-12-31T23:59:59.999Z"
        },
        "excludeDomains": {
          "description": "Optional list of domains to exclude from results",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "includeDomains": {
          "description": "Optional list of domains to include in results",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "numResults": {
          "description": "Maximum results (1-20, default 10)",
          "type": "integer",
          "default": 10,
          "maximum": 20,
          "minimum": 1,
          "example": 5
        },
        "query": {
          "description": "The search query",
          "type": "string",
          "example": "best street food in Mumbai"
        },
        "startPublishedDate": {
          "description": "Optional lower bound on publishedDate (RFC3339 or YYYY-MM-DD)",
          "type": "string",
          "example": "2025-01-01T00:00:00.000Z"
        },
        "text": {
          "description": "Optional top-level text option (boolean or {\"maxCharacters\": n}) for search_and_contents",
          "type": "object"
        },
        "userLocation": {
          "description": "Optional 2-letter ISO country code (e.g. \"US\", \"DE\")",
          "type": "string",
          "example": "US"
        }
      }
    },
    "publicapi.ExaSearchResponse": {
      "type": "object",
      "properties": {
        "requestId": {
          "type": "string"
        },
        "results": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/publicapi.ExaSearchResult"
          }
        }
      }
    },
    "publicapi.ExaSearchResult": {
      "type": "object",
      "properties": {
        "author": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "publishedDate": {
          "type": "string"
        },
        "text": {
          "description": "Result snippet when contents.text or text is requested",
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "url": {
          "type": "string"
        }
      }
    },
    "publicapi.ExaStatusError": {
      "type": "object",
      "properties": {
        "httpStatusCode": {
          "type": "integer"
        },
        "tag": {
          "type": "string",
          "enum": [
            "CRAWL_HTTP_4XX",
            "CRAWL_TIMEOUT",
            "CRAWL_UNKNOWN_ERROR",
            "UNSUPPORTED_URL"
          ]
        }
      }
    },
    "publicapi.FeedbackRequest": {
      "type": "object",
      "required": [
        "request_id"
      ],
      "properties": {
        "comment": {
          "description": "Optional free-text details about the issue",
          "type": "string",
          "example": "Returned a challenge page instead of article content"
        },
        "reason": {
          "description": "Category of the bad result: blocked, empty, wrong_content, timeout, or other (defaults to other)",
          "type": "string",
          "enum": [
            "blocked",
            "empty",
            "wrong_content",
            "timeout",
            "other"
          ],
          "example": "blocked"
        },
        "request_id": {
          "description": "The request_id of the crawl, research or VSS response being reported",
          "type": "string",
          "example": "req_9f1c2a7e4b8d4c0fa1e2b3c4d5e6f708"
        },
        "url": {
          "description": "Optional URL that was requested",
          "type": "string",
          "example": "https://example.com"
        }
      }
    },
    "publicapi.FeedbackResponse": {
      "type": "object",
      "properties": {
        "request_id": {
          "type": "string",
          "example": "req_9f1c2a7e4b8d4c0fa1e2b3c4d5e6f708"
        },
        "status": {
          "type": "string",
          "example": "received"
        }
      }
    },
    "publicapi.FirecrawlDocument": {
      "type": "object",
      "properties": {
        "html": {
          "type": "string"
        },
        "links": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "markdown": {
          "type": "string"
        },
        "metadata": {
          "$ref": "#/definitions/publicapi.FirecrawlMetadata"
        },
        "rawHtml": {
          "type": "string"
        }
      }
    },
    "publicapi.FirecrawlError": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string",
          "enum": [
            "BAD_REQUEST",
            "UNSUPPORTED_FORMAT",
            "UNAUTHORIZED",
            "FORBIDDEN",
            "SCRAPE_TIMEOUT",
            "SCRAPE_FAILED",
            "INTERNAL_ERROR"
          ]
        },
        "error": {
          "type": "string"
        },
        "success": {
          "type": "boolean",
          "example": false
        }
      }
    },
    "publicapi.FirecrawlLocation": {
      "type": "object",
      "properties": {
        "country": {
          "type": "string",
          "example": "US"
        }
      }
    },
    "publicapi.FirecrawlMetadata": {
      "type": "object",
      "properties": {
        "contentType": {
          "type": "string",
          "example": "text/html"
        },
        "scrapeId": {
          "description": "UpRock crawl job ID",
          "type": "string"
        },
        "sourceURL": {
          "description": "The requested URL",
          "type": "string",
          "example": "https://example.com"
        },
        "statusCode": {
          "description": "HTTP status code of the page",
          "type": "integer",
          "example": 200
        },
        "title": {
          "type": "string",
          "example": "Example Domain"
        },
        "url": {
          "description": "The URL that was crawled",
          "type": "string",
          "example": "https://example.com"
        }
      }
    },
    "publicapi.FirecrawlScrapeRequest": {
      "type": "object",
      "required": [
        "url"
      ],
      "properties": {
        "formats": {
          "description": "Output formats, as strings or v2 objects ({\"type\": \"markdown\"}). Supported: markdown (default), html, rawHtml, links; any other format returns 400 UNSUPPORTED_FORMAT",
          "type": "array",
          "items": {
            "type": "string"
          },
          "example": [
            "markdown",
            "html"
          ]
        },
        "location": {
          "description": "Where to crawl from; only country (ISO 3166-1 alpha-2) is used",
          "allOf": [
            {
              "$ref": "#/definitions/publicapi.FirecrawlLocation"
            }
          ]
        },
        "mobile": {
          "description": "Crawl on a mobile device (billed as crawl_mobile)",
          "type": "boolean"
        },
        "onlyMainContent": {
          "description": "Markdown of the main content only (default true); false converts the whole page",
          "type": "boolean"
        },
        "timeout": {
          "description": "Crawl budget in milliseconds (v2 default 60000, v1 default 30000), clamped to 10000-90000",
          "type": "integer",
          "example": 30000
        },
        "url": {
          "description": "URL to scrape",
          "type": "string",
          "example": "https://example.com"
        }
      }
    },
    "publicapi.FirecrawlScrapeResponse": {
      "type": "object",
      "properties": {
        "data": {
          "$ref": "#/definitions/publicapi.FirecrawlDocument"
        },
        "success": {
          "type": "boolean",
          "example": true
        }
      }
    },
    "publicapi.ResearchError": {
      "type": "object",
      "properties": {
        "credits_left": {
          "type": "number"
        },
        "credits_used": {
          "type": "number",
          "example": 0
        },
        "error": {
          "$ref": "#/definitions/publicapi.ResearchErrorDetail"
        },
        "next_step": {
          "type": "string",
          "example": "Get an API key at https://uprock.ai/auth.md or run: uprock auth login --setup-ai"
        },
        "request_id": {
          "type": "string",
          "example": "req_9f1c2a7e4b8d4c0fa1e2b3c4d5e6f708"
        }
      }
    },
    "publicapi.ResearchErrorDetail": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string",
          "example": "missing_api_key"
        },
        "message": {
          "type": "string"
        }
      }
    },
    "publicapi.ResearchRequest": {
      "type": "object",
      "required": [
        "query"
      ],
      "properties": {
        "max_results": {
          "description": "Maximum results (default 12, max 50)",
          "type": "integer",
          "default": 12,
          "maximum": 50,
          "minimum": 1,
          "example": 12
        },
        "num_sources": {
          "description": "Maximum number of countries to query (default 5, max 20)",
          "type": "integer",
          "default": 5,
          "maximum": 20,
          "minimum": 1,
          "example": 5
        },
        "query": {
          "description": "What to research",
          "type": "string",
          "example": "best street food in Mumbai"
        },
        "suggested_countries": {
          "description": "Where to search from: meta-regions (NA, EU, APAC, LATAM, MEA) or ISO 3166-1 alpha-2 codes. Omit to let the server choose",
          "type": "array",
          "items": {
            "type": "string"
          },
          "example": [
            "IN"
          ]
        }
      }
    },
    "publicapi.ResearchResponse": {
      "type": "object",
      "properties": {
        "count": {
          "type": "integer",
          "example": 12
        },
        "credits_left": {
          "type": "number",
          "example": 4999
        },
        "credits_used": {
          "type": "number",
          "example": 1
        },
        "query": {
          "type": "string",
          "example": "best street food in Mumbai"
        },
        "request_id": {
          "type": "string",
          "example": "req_9f1c2a7e4b8d4c0fa1e2b3c4d5e6f708"
        },
        "results": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/publicapi.ResearchResult"
          }
        }
      }
    },
    "publicapi.ResearchResult": {
      "type": "object",
      "properties": {
        "date_published": {
          "description": "ISO 8601 date from the result snippet, or null",
          "type": "string"
        },
        "description": {
          "type": "string"
        },
        "domain": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "url": {
          "type": "string"
        }
      }
    },
    "publicapi.TavilyError": {
      "type": "object",
      "properties": {
        "detail": {
          "type": "object",
          "properties": {
            "error": {
              "type": "string"
            }
          }
        }
      }
    },
    "publicapi.TavilyExtractRequest": {
      "type": "object",
      "required": [
        "urls"
      ],
      "properties": {
        "api_key": {
          "description": "UpRock API key, if not sent as Authorization: Bearer",
          "type": "string"
        },
        "format": {
          "description": "Output format of raw_content: markdown (default) or text",
          "type": "string",
          "default": "markdown",
          "enum": [
            "markdown",
            "text"
          ]
        },
        "include_images": {
          "description": "Accepted for compatibility; images is always an empty list",
          "type": "boolean"
        },
        "include_usage": {
          "description": "Include usage.credits (pages billed) in the response",
          "type": "boolean"
        },
        "timeout": {
          "description": "Seconds the request may take (default 30); URLs are crawled for this minus 3s, clamped to 10-90",
          "type": "number",
          "example": 30
        },
        "urls": {
          "description": "One URL or a list of up to 20 URLs",
          "type": "array",
          "items": {
            "type": "string"
          },
          "example": [
            "https://example.com"
          ]
        }
      }
    },
    "publicapi.TavilyExtractResponse": {
      "type": "object",
      "properties": {
        "failed_results": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/publicapi.TavilyFailedResult"
          }
        },
        "request_id": {
          "type": "string"
        },
        "response_time": {
          "description": "Seconds taken",
          "type": "number",
          "example": 2.4
        },
        "results": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/publicapi.TavilyExtractResult"
          }
        },
        "usage": {
          "$ref": "#/definitions/publicapi.TavilyUsage"
        }
      }
    },
    "publicapi.TavilyExtractResult": {
      "type": "object",
      "properties": {
        "images": {
          "description": "Present (always empty) only when include_images is set",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "raw_content": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "url": {
          "type": "string"
        }
      }
    },
    "publicapi.TavilyFailedResult": {
      "type": "object",
      "properties": {
        "error": {
          "type": "string"
        },
        "url": {
          "type": "string"
        }
      }
    },
    "publicapi.TavilySearchRequest": {
      "type": "object",
      "required": [
        "query"
      ],
      "properties": {
        "api_key": {
          "description": "UpRock API key, if not sent as Authorization: Bearer",
          "type": "string"
        },
        "country": {
          "description": "Country to search from, as Tavily's lowercase country name (e.g. \"united states\")",
          "type": "string",
          "example": "india"
        },
        "exclude_domains": {
          "description": "Optional list of domains to exclude from results",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "include_domains": {
          "description": "Optional list of domains to include in results",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "include_images": {
          "description": "Accepted for compatibility; images is always an empty list",
          "type": "boolean"
        },
        "max_results": {
          "description": "Maximum results (0-20, default 10)",
          "type": "integer",
          "default": 10,
          "maximum": 20,
          "minimum": 0,
          "example": 5
        },
        "query": {
          "description": "The search query",
          "type": "string",
          "example": "best street food in Mumbai"
        }
      }
    },
    "publicapi.TavilySearchResponse": {
      "type": "object",
      "properties": {
        "answer": {
          "description": "Not supported; always null",
          "type": "string"
        },
        "follow_up_questions": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "images": {
          "description": "Always empty",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "query": {
          "type": "string"
        },
        "request_id": {
          "type": "string"
        },
        "response_time": {
          "type": "number",
          "example": 3.2
        },
        "results": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/publicapi.TavilySearchResult"
          }
        }
      }
    },
    "publicapi.TavilySearchResult": {
      "type": "object",
      "properties": {
        "content": {
          "description": "The result's snippet",
          "type": "string"
        },
        "published_date": {
          "type": "string"
        },
        "raw_content": {
          "description": "Not supported; always null",
          "type": "string"
        },
        "score": {
          "description": "Rank-based score in (0, 1], highest first; not a relevance-model score",
          "type": "number"
        },
        "title": {
          "type": "string"
        },
        "url": {
          "type": "string"
        }
      }
    },
    "publicapi.TavilyUsage": {
      "type": "object",
      "properties": {
        "credits": {
          "type": "integer"
        }
      }
    },
    "publicapi.TrialInfo": {
      "type": "object",
      "properties": {
        "get_api_key": {
          "description": "Where to get an API key for more crawls, mobile devices and longer timeouts",
          "type": "string",
          "example": "https://uprock.ai/app/api-keys"
        },
        "limit": {
          "description": "Keyless crawls allowed per client IP per UTC day",
          "type": "integer",
          "example": 10
        },
        "remaining": {
          "description": "Keyless crawls left today for this client",
          "type": "integer",
          "example": 9
        },
        "resets_at": {
          "description": "When the count resets (the next UTC midnight)",
          "type": "string",
          "example": "2026-10-27T00:00:00Z"
        }
      }
    },
    "vss.httpErrorResponse": {
      "type": "object",
      "properties": {
        "credits_left": {
          "type": "number"
        },
        "credits_used": {
          "type": "number"
        },
        "error": {
          "type": "string"
        },
        "next_step": {
          "type": "string"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "vss.momentLine": {
      "type": "object",
      "properties": {
        "canonical_url": {
          "type": "string"
        },
        "credits_left": {
          "type": "number"
        },
        "credits_used": {
          "type": "number"
        },
        "duration_s": {
          "type": "number"
        },
        "error": {
          "type": "string"
        },
        "has_more": {
          "type": "boolean"
        },
        "index": {
          "type": "integer"
        },
        "moment_key": {
          "type": "string"
        },
        "next_offset_s": {
          "type": "number"
        },
        "published_at": {
          "type": "integer"
        },
        "request_id": {
          "type": "string"
        },
        "source": {
          "type": "string"
        },
        "status": {
          "type": "string"
        },
        "t0_s": {
          "type": "number"
        },
        "t1_s": {
          "type": "number"
        },
        "text": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "total_moments": {
          "type": "integer"
        },
        "video_id": {
          "type": "string"
        }
      }
    },
    "vss.momentMissResponse": {
      "type": "object",
      "properties": {
        "crawl_job_id": {
          "type": "string"
        },
        "credits_left": {
          "type": "number"
        },
        "credits_used": {
          "type": "number"
        },
        "duration_s": {
          "type": "number"
        },
        "message": {
          "type": "string"
        },
        "next_step": {
          "type": "string"
        },
        "request_id": {
          "type": "string"
        },
        "status": {
          "type": "string"
        },
        "video_id": {
          "type": "string"
        }
      }
    },
    "vss.momentRequestBody": {
      "type": "object",
      "properties": {
        "end_s": {
          "type": "number"
        },
        "force_regen": {
          "type": "boolean"
        },
        "max_moments": {
          "type": "integer"
        },
        "public_only": {
          "type": "boolean"
        },
        "recipe_version": {
          "type": "string"
        },
        "resolution": {
          "type": "integer"
        },
        "start_s": {
          "type": "number"
        },
        "two_pass": {
          "type": "boolean"
        },
        "url": {
          "type": "string"
        }
      }
    },
    "vss.searchRequestBody": {
      "type": "object",
      "properties": {
        "limit": {
          "type": "integer"
        },
        "mode": {
          "type": "string"
        },
        "order_by_recency": {
          "type": "boolean"
        },
        "public_only": {
          "type": "boolean"
        },
        "published_after": {
          "type": "integer"
        },
        "published_before": {
          "type": "integer"
        },
        "query": {
          "type": "string"
        },
        "tags": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        }
      }
    },
    "vss.searchResponseBody": {
      "type": "object",
      "properties": {
        "credits_left": {
          "type": "number"
        },
        "credits_used": {
          "type": "number"
        },
        "request_id": {
          "type": "string"
        },
        "results": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/vss.searchResult"
          }
        }
      }
    },
    "vss.searchResult": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "occurrence_count": {
          "type": "integer"
        },
        "origin_url": {
          "description": "OriginURL is the original source page (YouTube/X/TikTok/...) as supplied at\ningest, surfaced so a client can render the embed/deep-link without a\nseparate id->URL lookup. Source is the origin platform string. Both \"\" when\nunknown.",
          "type": "string"
        },
        "published_at": {
          "description": "PublishedAt is the video's original publication time (unix seconds, UTC), 0\nwhen unknown \u2014 the field order_by_recency sorts on; lets a client show the date.",
          "type": "integer"
        },
        "score": {
          "type": "number"
        },
        "source": {
          "type": "string"
        },
        "t0": {
          "type": "number"
        },
        "t1": {
          "type": "number"
        },
        "title": {
          "type": "string"
        },
        "transcript": {
          "type": "string"
        },
        "video_id": {
          "type": "string"
        }
      }
    }
  },
  "securityDefinitions": {
    "Bearer": {
      "description": "Header based bearer auth keys",
      "type": "apiKey",
      "name": "Authorization",
      "in": "header"
    }
  },
  "host": "edge.uprock.com",
  "schemes": [
    "https"
  ]
}
