{
  "openapi": "3.0.3",
  "info": {
    "title": "Synthient API",
    "version": "1.0.0",
    "description": "Synthient API for IP enrichment, account management, and real-time data feeds.\n\n## Authentication\n\nAll endpoints (except `/health`) require an API key passed in the `X-Api-Key` header.\n\n## Feed Types\n\nEach feed exposes two access patterns:\n- **Stream** (`/stream`): Real-time NDJSON stream of live events. Connections stay open for up to 30 minutes; an empty NDJSON line is sent every ~15s as a transport-level keepalive that NDJSON parsers should skip.\n- **Export** (`/export`): Pre-built parquet snapshots produced hourly and rolled up daily — list, download, and inspect metadata.\n\nEach feed has its own dedicated section in the sidebar with full schema, scope, and cadence reference. Honeypot feeds are grouped under **Helios**.\n\n## Timestamps\n\nEvery timestamp on the HTTP surface is encoded as Unix seconds (UTC, integer). The same instants on the gRPC surface are encoded as `google.protobuf.Timestamp` (RFC 3339 strings under protojson, structured `{seconds, nanos}` under proto3 binary). Both representations refer to the same instant — only the encoding differs.\n\n## API Versioning\n\nThe HTTP base path `/api/v4` is the *HTTP surface revision*. The gRPC service is `synthient.v1.SynthientService`. The two version numbers are decoupled by design — they were not bumped in lockstep historically and are not now. A breaking change to the gRPC contract bumps the proto package (v2, v3, …); a breaking change to the HTTP surface bumps the path revision (`/api/v5`).\n\n## gRPC API\n\nAll HTTP endpoints have equivalent gRPC methods. The public gRPC endpoint is `grpc.synthient.com:443` (TLS). Do not use `api.synthient.com` or port `50051` — the internal gRPC port is not exposed publicly.\n\nExample:\n\n```\ngrpcurl -H 'x-api-key: YOUR_KEY' grpc.synthient.com:443 list\n```\n\nSee the `synthient.v1.SynthientService` protobuf service definition for the full gRPC API. Authentication uses the `x-api-key` gRPC metadata key. Rate-limit rejections additionally carry a `retry-after` metadata field mirroring the HTTP `Retry-After` header."
  },
  "servers": [
    {
      "url": "/api/v4",
      "description": "Versioned HTTP API base path"
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Account info, quota, and the public health probe."
    },
    {
      "name": "Lookup",
      "description": "Single-IP, batch-IP, and domain enrichment endpoints."
    },
    {
      "name": "Proxies",
      "description": "Residential, datacenter, mobile, Tor, and VPN proxy IPs. One real-time stream and one parquet export."
    },
    {
      "name": "Anonymizers",
      "description": "VPN, Tor, and relay-class anonymizer IP ranges. One real-time stream and one parquet export."
    },
    {
      "name": "Torrents",
      "description": "DHT and tracker peer observations. One real-time stream and one parquet export."
    },
    {
      "name": "Helios",
      "description": "Honeypot capture streams. Helios sensors record HTTP requests, TLS ClientHellos, and ADB shell commands from inbound traffic to our honeypot tunnels. Each protocol has its own real-time stream and parquet export."
    },
    {
      "name": "JA4T",
      "description": "JA4T TCP-layer fingerprint sightings attributed to source IPs with provider, country, and ASN enrichment. One real-time stream and one parquet export."
    }
  ],
  "paths": {
    "/account/me": {
      "get": {
        "operationId": "getAccountInfo",
        "summary": "Get account info",
        "description": "Return the authenticated account's scopes and remaining quota.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Account info",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountInfoResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/anonymizers/export": {
      "get": {
        "operationId": "listAnonymizersExport",
        "summary": "List anonymizers parquet exports",
        "description": "List parquet export feeds for the **anonymizers** stream. Returns one page of daily and hourly snapshots, newest-first; pass `next_cursor` back as `cursor` for the next page.\n\n**Required scope:** `ANONYMIZERS_FEED`. Parquet snapshots land at `{stream}/latest/HH_{stream}.parquet` hourly and are rolled up daily into `{stream}/YYYY/MM/DD/{stream}.parquet` (00:30 UTC).",
        "tags": [
          "Anonymizers"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "description": "Page size. Defaults to 100; values above 500 are clamped."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination token returned in `next_cursor` of the previous response. Omit on the first page."
          }
        ],
        "responses": {
          "200": {
            "description": "Page of export feeds. Includes `next_cursor` when more pages exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad cursor or invalid limit",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `ANONYMIZERS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/anonymizers/export/{date}": {
      "get": {
        "operationId": "downloadAnonymizersExport",
        "summary": "Download a anonymizers parquet export",
        "description": "Download a single parquet snapshot for **anonymizers**. Returns a 307 redirect to a presigned R2 URL valid for 24 hours. 307 (not 301) because the URL is minted per request and expires — intermediaries must not cache it.\n\n**Required scope:** `ANONYMIZERS_FEED`. **Columns:** `range_start`, `range_end`, `provider`, `type`, `country_code`, `asn`, `timestamp`",
        "tags": [
          "Anonymizers"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format (expected YYYY-MM-DD or 'latest')",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `ANONYMIZERS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/anonymizers/export/{date}/meta": {
      "get": {
        "operationId": "getAnonymizersExportMeta",
        "summary": "Get anonymizers parquet metadata",
        "description": "Metadata for a single **anonymizers** parquet snapshot: SHA-256 checksum, byte size, row count, parquet schema (column names + types), and canonical date. No file body — call the matching `/export/{date}` endpoint to download.\n\n**Required scope:** `ANONYMIZERS_FEED`.",
        "tags": [
          "Anonymizers"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `ANONYMIZERS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/anonymizers/export/{date}/{hour}": {
      "get": {
        "operationId": "downloadAnonymizersHourlyExport",
        "summary": "Download a anonymizers hourly parquet export",
        "description": "Download a specific hourly parquet snapshot for **anonymizers** by date and hour. Returns a 307 redirect to a presigned R2 URL valid for 24 hours.\n\nHourly files are transient — shortly after 00:30 UTC they are rolled up into the daily export and the per-hour artifacts are deleted. Hours remain downloadable until their day's rollup completes (including yesterday's hours in the window right after midnight UTC); once rolled up, requests return 400 with a note pointing at `/export/{date}` for the daily rollup. The `latest` alias rolls forward each hour and tracks the most recent hourly.\n\n**Required scope:** `ANONYMIZERS_FEED`. **Columns:** `range_start`, `range_end`, `provider`, `type`, `country_code`, `asn`, `timestamp`",
        "tags": [
          "Anonymizers"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or the hour has already been rolled into the daily export (request /export/{date} instead)",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `ANONYMIZERS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/anonymizers/export/{date}/{hour}/meta": {
      "get": {
        "operationId": "getAnonymizersHourlyExportMeta",
        "summary": "Get anonymizers hourly parquet metadata",
        "description": "Metadata for one hourly **anonymizers** parquet snapshot identified by date and hour. Same shape as the daily `/meta` response.\n\nHourly metadata is available while the hour's file exists — including yesterday's hours until their daily rollup completes. Once rolled up, request the daily `/export/{date}/meta` instead.\n\n**Required scope:** `ANONYMIZERS_FEED`.",
        "tags": [
          "Anonymizers"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or date is not the current UTC date",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `ANONYMIZERS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/anonymizers/stream": {
      "get": {
        "operationId": "streamAnonymizers",
        "summary": "Stream anonymizer events in real time",
        "description": "Real-time NDJSON stream of VPN, Tor, and relay-class anonymizer observations. Each event is an IP range (start/end) rather than an individual address — VPN providers issue blocks, not single IPs.\n\n**Required scope:** `ANONYMIZERS_STREAM`. **Schema:** see `AnonymizerStreamEntry` under Components.\n\n**Sample event:**\n```\n{\"range_start\":\"198.51.100.0\",\"range_end\":\"198.51.100.255\",\"provider\":\"nordvpn\",\"type\":\"commercial_vpn\",\"timestamp\":1714780800,\"country_code\":\"NL\",\"asn\":60068}\n```",
        "tags": [
          "Anonymizers"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "NDJSON stream of anonymizers events. Each line is a complete JSON object. The connection stays open for up to 30 minutes.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "$ref": "#/components/schemas/AnonymizerStreamEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks the required scope for this feed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too many concurrent streams from this client",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable — streaming backend is down",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/adb/export": {
      "get": {
        "operationId": "listHoneypotADBExport",
        "summary": "List honeypot_adb parquet exports",
        "description": "List parquet export feeds for the **honeypot_adb** stream. Returns one page of daily and hourly snapshots, newest-first; pass `next_cursor` back as `cursor` for the next page.\n\n**Required scope:** `HONEYPOT_ADB_FEED`. Parquet snapshots land at `{stream}/latest/HH_{stream}.parquet` hourly and are rolled up daily into `{stream}/YYYY/MM/DD/{stream}.parquet` (00:30 UTC).",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "description": "Page size. Defaults to 100; values above 500 are clamped."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination token returned in `next_cursor` of the previous response. Omit on the first page."
          }
        ],
        "responses": {
          "200": {
            "description": "Page of export feeds. Includes `next_cursor` when more pages exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad cursor or invalid limit",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_ADB_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/adb/export/{date}": {
      "get": {
        "operationId": "downloadHoneypotADBExport",
        "summary": "Download a honeypot_adb parquet export",
        "description": "Download a single parquet snapshot for **honeypot_adb**. Returns a 307 redirect to a presigned R2 URL valid for 24 hours. 307 (not 301) because the URL is minted per request and expires — intermediaries must not cache it.\n\n**Required scope:** `HONEYPOT_ADB_FEED`. **Columns:** `session`, `sequential_id`, `command` (raw bytes), `hash`.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format (expected YYYY-MM-DD or 'latest')",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_ADB_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/adb/export/{date}/meta": {
      "get": {
        "operationId": "getHoneypotADBExportMeta",
        "summary": "Get honeypot_adb parquet metadata",
        "description": "Metadata for a single **honeypot_adb** parquet snapshot: SHA-256 checksum, byte size, row count, parquet schema (column names + types), and canonical date. No file body — call the matching `/export/{date}` endpoint to download.\n\n**Required scope:** `HONEYPOT_ADB_FEED`.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_ADB_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/adb/export/{date}/{hour}": {
      "get": {
        "operationId": "downloadHoneypotADBHourlyExport",
        "summary": "Download a honeypot_adb hourly parquet export",
        "description": "Download a specific hourly parquet snapshot for **honeypot_adb** by date and hour. Returns a 307 redirect to a presigned R2 URL valid for 24 hours.\n\nHourly files are transient — shortly after 00:30 UTC they are rolled up into the daily export and the per-hour artifacts are deleted. Hours remain downloadable until their day's rollup completes (including yesterday's hours in the window right after midnight UTC); once rolled up, requests return 400 with a note pointing at `/export/{date}` for the daily rollup. The `latest` alias rolls forward each hour and tracks the most recent hourly.\n\n**Required scope:** `HONEYPOT_ADB_FEED`. **Columns:** `session`, `sequential_id`, `command` (raw bytes), `hash`.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or the hour has already been rolled into the daily export (request /export/{date} instead)",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_ADB_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/adb/export/{date}/{hour}/meta": {
      "get": {
        "operationId": "getHoneypotADBHourlyExportMeta",
        "summary": "Get honeypot_adb hourly parquet metadata",
        "description": "Metadata for one hourly **honeypot_adb** parquet snapshot identified by date and hour. Same shape as the daily `/meta` response.\n\nHourly metadata is available while the hour's file exists — including yesterday's hours until their daily rollup completes. Once rolled up, request the daily `/export/{date}/meta` instead.\n\n**Required scope:** `HONEYPOT_ADB_FEED`.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or date is not the current UTC date",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_ADB_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/adb/stream": {
      "get": {
        "operationId": "streamHoneypotADB",
        "summary": "Stream Helios honeypot ADB shell commands in real time",
        "description": "Real-time NDJSON stream of Android Debug Bridge shell commands captured by Helios honeypot sensors. Each event is one command in a session, ordered by `sequential_id`. Use the `session` field to reconstruct the full attacker shell transcript.\n\n**Required scope:** `HONEYPOT_ADB_STREAM`. **Schema:** see `HoneypotADBStreamEntry` under Components.\n\n**Sample event:**\n```\n{\"session\":\"sha256:abc…\",\"sequential_id\":3,\"command\":\"d2dldCBodHRwOi8vMTAuMC4wLjEvbWlyYWk=\",\"hash\":\"7c4a8d09ca3762af61e59520943dc26494f8941b\"}\n```",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "NDJSON stream of honeypot_adb events. Each line is a complete JSON object. The connection stays open for up to 30 minutes.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "$ref": "#/components/schemas/HoneypotADBStreamEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks the required scope for this feed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too many concurrent streams from this client",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable — streaming backend is down",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/dns/export": {
      "get": {
        "operationId": "listHoneypotDNSExport",
        "summary": "List honeypot_dns parquet exports",
        "description": "List parquet export feeds for the **honeypot_dns** stream. Returns one page of daily and hourly snapshots, newest-first; pass `next_cursor` back as `cursor` for the next page.\n\n**Required scope:** `HONEYPOT_DNS_FEED`. Parquet snapshots land at `{stream}/latest/HH_{stream}.parquet` hourly and are rolled up daily into `{stream}/YYYY/MM/DD/{stream}.parquet` (00:30 UTC).",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "description": "Page size. Defaults to 100; values above 500 are clamped."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination token returned in `next_cursor` of the previous response. Omit on the first page."
          }
        ],
        "responses": {
          "200": {
            "description": "Page of export feeds. Includes `next_cursor` when more pages exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad cursor or invalid limit",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_DNS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/dns/export/{date}": {
      "get": {
        "operationId": "downloadHoneypotDNSExport",
        "summary": "Download a honeypot_dns parquet export",
        "description": "Download a single parquet snapshot for **honeypot_dns**. Returns a 307 redirect to a presigned R2 URL valid for 24 hours. 307 (not 301) because the URL is minted per request and expires — intermediaries must not cache it.\n\n**Required scope:** `HONEYPOT_DNS_FEED`. **Columns:** `timestamp`, `tunnel_id`, `domain`, `port`. Meta is omitted from the parquet (the projector does not handle nested messages).",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format (expected YYYY-MM-DD or 'latest')",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_DNS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/dns/export/{date}/meta": {
      "get": {
        "operationId": "getHoneypotDNSExportMeta",
        "summary": "Get honeypot_dns parquet metadata",
        "description": "Metadata for a single **honeypot_dns** parquet snapshot: SHA-256 checksum, byte size, row count, parquet schema (column names + types), and canonical date. No file body — call the matching `/export/{date}` endpoint to download.\n\n**Required scope:** `HONEYPOT_DNS_FEED`.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_DNS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/dns/export/{date}/{hour}": {
      "get": {
        "operationId": "downloadHoneypotDNSHourlyExport",
        "summary": "Download a honeypot_dns hourly parquet export",
        "description": "Download a specific hourly parquet snapshot for **honeypot_dns** by date and hour. Returns a 307 redirect to a presigned R2 URL valid for 24 hours.\n\nHourly files are transient — shortly after 00:30 UTC they are rolled up into the daily export and the per-hour artifacts are deleted. Hours remain downloadable until their day's rollup completes (including yesterday's hours in the window right after midnight UTC); once rolled up, requests return 400 with a note pointing at `/export/{date}` for the daily rollup. The `latest` alias rolls forward each hour and tracks the most recent hourly.\n\n**Required scope:** `HONEYPOT_DNS_FEED`. **Columns:** `timestamp`, `tunnel_id`, `domain`, `port`. Meta is omitted from the parquet (the projector does not handle nested messages).",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or the hour has already been rolled into the daily export (request /export/{date} instead)",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_DNS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/dns/export/{date}/{hour}/meta": {
      "get": {
        "operationId": "getHoneypotDNSHourlyExportMeta",
        "summary": "Get honeypot_dns hourly parquet metadata",
        "description": "Metadata for one hourly **honeypot_dns** parquet snapshot identified by date and hour. Same shape as the daily `/meta` response.\n\nHourly metadata is available while the hour's file exists — including yesterday's hours until their daily rollup completes. Once rolled up, request the daily `/export/{date}/meta` instead.\n\n**Required scope:** `HONEYPOT_DNS_FEED`.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or date is not the current UTC date",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_DNS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/dns/stream": {
      "get": {
        "operationId": "streamHoneypotDNS",
        "summary": "Stream Helios honeypot DNS queries in real time",
        "description": "Real-time NDJSON stream of DNS queries directed at Helios honeypot tunnels. Each event records the queried domain, the destination port (53 for plain DNS, 853 for DoT, 5353 for mDNS), and the proxy/server/pool metadata of the inbound tunnel.\n\n**Required scope:** `HONEYPOT_DNS_STREAM`. **Schema:** see `HoneypotDNSStreamEntry` under Components.\n\n**Sample event:**\n```\n{\"timestamp\":1714780800,\"tunnel_id\":42,\"domain\":\"command-and-control.example\",\"port\":53,\"meta\":{\"proxy_ip\":\"203.0.113.10\",\"server\":\"hp-eu-1\",\"pool_id\":\"prod\",\"provider\":\"luminati\"}}\n```",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "NDJSON stream of honeypot_dns events. Each line is a complete JSON object. The connection stays open for up to 30 minutes.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "$ref": "#/components/schemas/HoneypotDNSStreamEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks the required scope for this feed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too many concurrent streams from this client",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable — streaming backend is down",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/http/export": {
      "get": {
        "operationId": "listHoneypotHTTPExport",
        "summary": "List honeypot_http parquet exports",
        "description": "List parquet export feeds for the **honeypot_http** stream. Returns one page of daily and hourly snapshots, newest-first; pass `next_cursor` back as `cursor` for the next page.\n\n**Required scope:** `HONEYPOT_HTTP_FEED`. Parquet snapshots land at `{stream}/latest/HH_{stream}.parquet` hourly and are rolled up daily into `{stream}/YYYY/MM/DD/{stream}.parquet` (00:30 UTC).",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "description": "Page size. Defaults to 100; values above 500 are clamped."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination token returned in `next_cursor` of the previous response. Omit on the first page."
          }
        ],
        "responses": {
          "200": {
            "description": "Page of export feeds. Includes `next_cursor` when more pages exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad cursor or invalid limit",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_HTTP_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/http/export/{date}": {
      "get": {
        "operationId": "downloadHoneypotHTTPExport",
        "summary": "Download a honeypot_http parquet export",
        "description": "Download a single parquet snapshot for **honeypot_http**. Returns a 307 redirect to a presigned R2 URL valid for 24 hours. 307 (not 301) because the URL is minted per request and expires — intermediaries must not cache it.\n\n**Required scope:** `HONEYPOT_HTTP_FEED`. **Columns:** `timestamp`, `tunnel_id`, `domain`, `port`, `method`, `uri`, `version`, `raw`. Headers and meta are omitted from the parquet (the projector does not handle maps or nested messages).",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format (expected YYYY-MM-DD or 'latest')",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_HTTP_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/http/export/{date}/meta": {
      "get": {
        "operationId": "getHoneypotHTTPExportMeta",
        "summary": "Get honeypot_http parquet metadata",
        "description": "Metadata for a single **honeypot_http** parquet snapshot: SHA-256 checksum, byte size, row count, parquet schema (column names + types), and canonical date. No file body — call the matching `/export/{date}` endpoint to download.\n\n**Required scope:** `HONEYPOT_HTTP_FEED`.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_HTTP_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/http/export/{date}/{hour}": {
      "get": {
        "operationId": "downloadHoneypotHTTPHourlyExport",
        "summary": "Download a honeypot_http hourly parquet export",
        "description": "Download a specific hourly parquet snapshot for **honeypot_http** by date and hour. Returns a 307 redirect to a presigned R2 URL valid for 24 hours.\n\nHourly files are transient — shortly after 00:30 UTC they are rolled up into the daily export and the per-hour artifacts are deleted. Hours remain downloadable until their day's rollup completes (including yesterday's hours in the window right after midnight UTC); once rolled up, requests return 400 with a note pointing at `/export/{date}` for the daily rollup. The `latest` alias rolls forward each hour and tracks the most recent hourly.\n\n**Required scope:** `HONEYPOT_HTTP_FEED`. **Columns:** `timestamp`, `tunnel_id`, `domain`, `port`, `method`, `uri`, `version`, `raw`. Headers and meta are omitted from the parquet (the projector does not handle maps or nested messages).",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or the hour has already been rolled into the daily export (request /export/{date} instead)",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_HTTP_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/http/export/{date}/{hour}/meta": {
      "get": {
        "operationId": "getHoneypotHTTPHourlyExportMeta",
        "summary": "Get honeypot_http hourly parquet metadata",
        "description": "Metadata for one hourly **honeypot_http** parquet snapshot identified by date and hour. Same shape as the daily `/meta` response.\n\nHourly metadata is available while the hour's file exists — including yesterday's hours until their daily rollup completes. Once rolled up, request the daily `/export/{date}/meta` instead.\n\n**Required scope:** `HONEYPOT_HTTP_FEED`.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or date is not the current UTC date",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_HTTP_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/http/stream": {
      "get": {
        "operationId": "streamHoneypotHTTP",
        "summary": "Stream Helios honeypot HTTP captures in real time",
        "description": "Real-time NDJSON stream of HTTP requests captured by Helios honeypot sensors. Each event includes method, URI, headers, version, and the raw request bytes plus the proxy/server/pool metadata of the inbound tunnel.\n\n**Required scope:** `HONEYPOT_HTTP_STREAM`. **Schema:** see `HoneypotHTTPStreamEntry` under Components.\n\n**Sample event:**\n```\n{\"timestamp\":1714780800,\"tunnel_id\":42,\"domain\":\"example.com\",\"port\":80,\"meta\":{\"proxy_ip\":\"203.0.113.10\",\"server\":\"hp-eu-1\",\"pool_id\":\"prod\",\"provider\":\"luminati\"},\"details\":{\"method\":\"GET\",\"uri\":\"/admin\",\"version\":\"HTTP/1.1\",\"headers\":{\"User-Agent\":\"curl/7.81.0\"}},\"raw\":\"GET /admin HTTP/1.1\\r\\n…\"}\n```",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "NDJSON stream of honeypot_http events. Each line is a complete JSON object. The connection stays open for up to 30 minutes.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "$ref": "#/components/schemas/HoneypotHTTPStreamEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks the required scope for this feed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too many concurrent streams from this client",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable — streaming backend is down",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/https/export": {
      "get": {
        "operationId": "listHoneypotHTTPSExport",
        "summary": "List honeypot_https parquet exports",
        "description": "List parquet export feeds for the **honeypot_https** stream. Returns one page of daily and hourly snapshots, newest-first; pass `next_cursor` back as `cursor` for the next page.\n\n**Required scope:** `HONEYPOT_HTTPS_FEED`. Parquet snapshots land at `{stream}/latest/HH_{stream}.parquet` hourly and are rolled up daily into `{stream}/YYYY/MM/DD/{stream}.parquet` (00:30 UTC).",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "description": "Page size. Defaults to 100; values above 500 are clamped."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination token returned in `next_cursor` of the previous response. Omit on the first page."
          }
        ],
        "responses": {
          "200": {
            "description": "Page of export feeds. Includes `next_cursor` when more pages exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad cursor or invalid limit",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_HTTPS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/https/export/{date}": {
      "get": {
        "operationId": "downloadHoneypotHTTPSExport",
        "summary": "Download a honeypot_https parquet export",
        "description": "Download a single parquet snapshot for **honeypot_https**. Returns a 307 redirect to a presigned R2 URL valid for 24 hours. 307 (not 301) because the URL is minted per request and expires — intermediaries must not cache it.\n\n**Required scope:** `HONEYPOT_HTTPS_FEED`. **Columns:** `timestamp`, `tunnel_id`, `domain`, `port`, `details` (JSON string), `raw` (bytes). Meta is omitted from the parquet.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format (expected YYYY-MM-DD or 'latest')",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_HTTPS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/https/export/{date}/meta": {
      "get": {
        "operationId": "getHoneypotHTTPSExportMeta",
        "summary": "Get honeypot_https parquet metadata",
        "description": "Metadata for a single **honeypot_https** parquet snapshot: SHA-256 checksum, byte size, row count, parquet schema (column names + types), and canonical date. No file body — call the matching `/export/{date}` endpoint to download.\n\n**Required scope:** `HONEYPOT_HTTPS_FEED`.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_HTTPS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/https/export/{date}/{hour}": {
      "get": {
        "operationId": "downloadHoneypotHTTPSHourlyExport",
        "summary": "Download a honeypot_https hourly parquet export",
        "description": "Download a specific hourly parquet snapshot for **honeypot_https** by date and hour. Returns a 307 redirect to a presigned R2 URL valid for 24 hours.\n\nHourly files are transient — shortly after 00:30 UTC they are rolled up into the daily export and the per-hour artifacts are deleted. Hours remain downloadable until their day's rollup completes (including yesterday's hours in the window right after midnight UTC); once rolled up, requests return 400 with a note pointing at `/export/{date}` for the daily rollup. The `latest` alias rolls forward each hour and tracks the most recent hourly.\n\n**Required scope:** `HONEYPOT_HTTPS_FEED`. **Columns:** `timestamp`, `tunnel_id`, `domain`, `port`, `details` (JSON string), `raw` (bytes). Meta is omitted from the parquet.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or the hour has already been rolled into the daily export (request /export/{date} instead)",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_HTTPS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/https/export/{date}/{hour}/meta": {
      "get": {
        "operationId": "getHoneypotHTTPSHourlyExportMeta",
        "summary": "Get honeypot_https hourly parquet metadata",
        "description": "Metadata for one hourly **honeypot_https** parquet snapshot identified by date and hour. Same shape as the daily `/meta` response.\n\nHourly metadata is available while the hour's file exists — including yesterday's hours until their daily rollup completes. Once rolled up, request the daily `/export/{date}/meta` instead.\n\n**Required scope:** `HONEYPOT_HTTPS_FEED`.",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or date is not the current UTC date",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `HONEYPOT_HTTPS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/helio/https/stream": {
      "get": {
        "operationId": "streamHoneypotHTTPS",
        "summary": "Stream Helios honeypot TLS ClientHello captures in real time",
        "description": "Real-time NDJSON stream of TLS ClientHello captures from Helios honeypot sensors. The `details` field is a JSON-encoded ClientHello (cipher suites, extensions, supported curves, ALPN); `raw` is the base64-encoded handshake bytes.\n\n**Required scope:** `HONEYPOT_HTTPS_STREAM`. **Schema:** see `HoneypotHTTPSStreamEntry` under Components.\n\n**Sample event:**\n```\n{\"timestamp\":1714780800,\"tunnel_id\":42,\"domain\":\"example.com\",\"port\":443,\"meta\":{\"proxy_ip\":\"203.0.113.10\",\"server\":\"hp-eu-1\",\"pool_id\":\"prod\",\"provider\":\"luminati\"},\"details\":\"{\\\"cipher_suites\\\":[4865,4866],\\\"server_name\\\":\\\"example.com\\\"}\",\"raw\":\"FgMBAH8BAA…\"}\n```",
        "tags": [
          "Helios"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "NDJSON stream of honeypot_https events. Each line is a complete JSON object. The connection stays open for up to 30 minutes.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "$ref": "#/components/schemas/HoneypotHTTPSStreamEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks the required scope for this feed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too many concurrent streams from this client",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable — streaming backend is down",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/ja4t/export": {
      "get": {
        "operationId": "listJA4TExport",
        "summary": "List ja4t parquet exports",
        "description": "List parquet export feeds for the **ja4t** stream. Returns one page of daily and hourly snapshots, newest-first; pass `next_cursor` back as `cursor` for the next page.\n\n**Required scope:** `JA4T_FEED`. Parquet snapshots land at `{stream}/latest/HH_{stream}.parquet` hourly and are rolled up daily into `{stream}/YYYY/MM/DD/{stream}.parquet` (00:30 UTC).",
        "tags": [
          "JA4T"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "description": "Page size. Defaults to 100; values above 500 are clamped."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination token returned in `next_cursor` of the previous response. Omit on the first page."
          }
        ],
        "responses": {
          "200": {
            "description": "Page of export feeds. Includes `next_cursor` when more pages exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad cursor or invalid limit",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `JA4T_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/ja4t/export/{date}": {
      "get": {
        "operationId": "downloadJA4TExport",
        "summary": "Download a ja4t parquet export",
        "description": "Download a single parquet snapshot for **ja4t**. Returns a 307 redirect to a presigned R2 URL valid for 24 hours. 307 (not 301) because the URL is minted per request and expires — intermediaries must not cache it.\n\n**Required scope:** `JA4T_FEED`. **Columns:** `ip`, `ja4t`, `provider`, `type`, `timestamp`, `country_code`, `asn`.",
        "tags": [
          "JA4T"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format (expected YYYY-MM-DD or 'latest')",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `JA4T_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/ja4t/export/{date}/meta": {
      "get": {
        "operationId": "getJA4TExportMeta",
        "summary": "Get ja4t parquet metadata",
        "description": "Metadata for a single **ja4t** parquet snapshot: SHA-256 checksum, byte size, row count, parquet schema (column names + types), and canonical date. No file body — call the matching `/export/{date}` endpoint to download.\n\n**Required scope:** `JA4T_FEED`.",
        "tags": [
          "JA4T"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `JA4T_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/ja4t/export/{date}/{hour}": {
      "get": {
        "operationId": "downloadJA4THourlyExport",
        "summary": "Download a ja4t hourly parquet export",
        "description": "Download a specific hourly parquet snapshot for **ja4t** by date and hour. Returns a 307 redirect to a presigned R2 URL valid for 24 hours.\n\nHourly files are transient — shortly after 00:30 UTC they are rolled up into the daily export and the per-hour artifacts are deleted. Hours remain downloadable until their day's rollup completes (including yesterday's hours in the window right after midnight UTC); once rolled up, requests return 400 with a note pointing at `/export/{date}` for the daily rollup. The `latest` alias rolls forward each hour and tracks the most recent hourly.\n\n**Required scope:** `JA4T_FEED`. **Columns:** `ip`, `ja4t`, `provider`, `type`, `timestamp`, `country_code`, `asn`.",
        "tags": [
          "JA4T"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or the hour has already been rolled into the daily export (request /export/{date} instead)",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `JA4T_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/ja4t/export/{date}/{hour}/meta": {
      "get": {
        "operationId": "getJA4THourlyExportMeta",
        "summary": "Get ja4t hourly parquet metadata",
        "description": "Metadata for one hourly **ja4t** parquet snapshot identified by date and hour. Same shape as the daily `/meta` response.\n\nHourly metadata is available while the hour's file exists — including yesterday's hours until their daily rollup completes. Once rolled up, request the daily `/export/{date}/meta` instead.\n\n**Required scope:** `JA4T_FEED`.",
        "tags": [
          "JA4T"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or date is not the current UTC date",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `JA4T_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/ja4t/stream": {
      "get": {
        "operationId": "streamJA4T",
        "summary": "Stream JA4T TCP fingerprint sightings in real time",
        "description": "Real-time NDJSON stream of JA4T (TCP fingerprint) sightings. Each event ties a source IP to a JA4T fingerprint along with provider, country, and ASN enrichment — the TCP-layer counterpart to the JA4 TLS fingerprint feed.\n\n**Required scope:** `JA4T_STREAM`. **Schema:** see `JA4TStreamEntry` under Components.\n\n**Sample event:**\n```\n{\"ip\":\"109.176.45.49\",\"ja4t\":\"43080_2-4-8-1-3_1436_9\",\"provider\":\"PROXYJET_DATACENTER\",\"type\":\"DATACENTER_PROXY\",\"timestamp\":1779818967,\"country_code\":\"US\",\"asn\":16276}\n```",
        "tags": [
          "JA4T"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "NDJSON stream of ja4t events. Each line is a complete JSON object. The connection stays open for up to 30 minutes.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "$ref": "#/components/schemas/JA4TStreamEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks the required scope for this feed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too many concurrent streams from this client",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable — streaming backend is down",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/proxies/export": {
      "get": {
        "operationId": "listProxiesExport",
        "summary": "List proxies parquet exports",
        "description": "List parquet export feeds for the **proxies** stream. Returns one page of daily and hourly snapshots, newest-first; pass `next_cursor` back as `cursor` for the next page.\n\n**Required scope:** `PROXY_FEEDS`. Parquet snapshots land at `{stream}/latest/HH_{stream}.parquet` hourly and are rolled up daily into `{stream}/YYYY/MM/DD/{stream}.parquet` (00:30 UTC).",
        "tags": [
          "Proxies"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "description": "Page size. Defaults to 100; values above 500 are clamped."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination token returned in `next_cursor` of the previous response. Omit on the first page."
          }
        ],
        "responses": {
          "200": {
            "description": "Page of export feeds. Includes `next_cursor` when more pages exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad cursor or invalid limit",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `PROXY_FEEDS`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/proxies/export/{date}": {
      "get": {
        "operationId": "downloadProxiesExport",
        "summary": "Download a proxies parquet export",
        "description": "Download a single parquet snapshot for **proxies**. Returns a 307 redirect to a presigned R2 URL valid for 24 hours. 307 (not 301) because the URL is minted per request and expires — intermediaries must not cache it.\n\n**Required scope:** `PROXY_FEEDS`. **Columns:** `ip`, `provider`, `type`, `timestamp`, `country_code`, `asn`",
        "tags": [
          "Proxies"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format (expected YYYY-MM-DD or 'latest')",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `PROXY_FEEDS`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/proxies/export/{date}/meta": {
      "get": {
        "operationId": "getProxiesExportMeta",
        "summary": "Get proxies parquet metadata",
        "description": "Metadata for a single **proxies** parquet snapshot: SHA-256 checksum, byte size, row count, parquet schema (column names + types), and canonical date. No file body — call the matching `/export/{date}` endpoint to download.\n\n**Required scope:** `PROXY_FEEDS`.",
        "tags": [
          "Proxies"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `PROXY_FEEDS`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/proxies/export/{date}/{hour}": {
      "get": {
        "operationId": "downloadProxiesHourlyExport",
        "summary": "Download a proxies hourly parquet export",
        "description": "Download a specific hourly parquet snapshot for **proxies** by date and hour. Returns a 307 redirect to a presigned R2 URL valid for 24 hours.\n\nHourly files are transient — shortly after 00:30 UTC they are rolled up into the daily export and the per-hour artifacts are deleted. Hours remain downloadable until their day's rollup completes (including yesterday's hours in the window right after midnight UTC); once rolled up, requests return 400 with a note pointing at `/export/{date}` for the daily rollup. The `latest` alias rolls forward each hour and tracks the most recent hourly.\n\n**Required scope:** `PROXY_FEEDS`. **Columns:** `ip`, `provider`, `type`, `timestamp`, `country_code`, `asn`",
        "tags": [
          "Proxies"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or the hour has already been rolled into the daily export (request /export/{date} instead)",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `PROXY_FEEDS`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/proxies/export/{date}/{hour}/meta": {
      "get": {
        "operationId": "getProxiesHourlyExportMeta",
        "summary": "Get proxies hourly parquet metadata",
        "description": "Metadata for one hourly **proxies** parquet snapshot identified by date and hour. Same shape as the daily `/meta` response.\n\nHourly metadata is available while the hour's file exists — including yesterday's hours until their daily rollup completes. Once rolled up, request the daily `/export/{date}/meta` instead.\n\n**Required scope:** `PROXY_FEEDS`.",
        "tags": [
          "Proxies"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or date is not the current UTC date",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `PROXY_FEEDS`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/proxies/stream": {
      "get": {
        "operationId": "streamProxies",
        "summary": "Stream proxy events in real time",
        "description": "Real-time NDJSON stream of proxy IP observations across residential, datacenter, mobile, Tor, and VPN providers. Each event is one IP sighting with provider attribution and geo enrichment.\n\n**Required scope:** `PROXY_FIREHOSE`. **Schema:** see `ProxyStreamEntry` under Components.\n\n**Sample event:**\n```\n{\"ip\":\"203.0.113.10\",\"provider\":\"luminati\",\"type\":\"residential\",\"timestamp\":1714780800,\"country_code\":\"US\",\"asn\":15169}\n```",
        "tags": [
          "Proxies"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "NDJSON stream of proxies events. Each line is a complete JSON object. The connection stays open for up to 30 minutes.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "$ref": "#/components/schemas/ProxyStreamEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks the required scope for this feed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too many concurrent streams from this client",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable — streaming backend is down",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/torrents/export": {
      "get": {
        "operationId": "listTorrentsExport",
        "summary": "List torrents parquet exports",
        "description": "List parquet export feeds for the **torrents** stream. Returns one page of daily and hourly snapshots, newest-first; pass `next_cursor` back as `cursor` for the next page.\n\n**Required scope:** `TORRENTS_FEED`. Parquet snapshots land at `{stream}/latest/HH_{stream}.parquet` hourly and are rolled up daily into `{stream}/YYYY/MM/DD/{stream}.parquet` (00:30 UTC).",
        "tags": [
          "Torrents"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "description": "Page size. Defaults to 100; values above 500 are clamped."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination token returned in `next_cursor` of the previous response. Omit on the first page."
          }
        ],
        "responses": {
          "200": {
            "description": "Page of export feeds. Includes `next_cursor` when more pages exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad cursor or invalid limit",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `TORRENTS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/torrents/export/{date}": {
      "get": {
        "operationId": "downloadTorrentsExport",
        "summary": "Download a torrents parquet export",
        "description": "Download a single parquet snapshot for **torrents**. Returns a 307 redirect to a presigned R2 URL valid for 24 hours. 307 (not 301) because the URL is minted per request and expires — intermediaries must not cache it.\n\n**Required scope:** `TORRENTS_FEED`. **Columns:** `info_hash`, `name`, `magnet_uri`, `total_size`, `piece_length`, `file_count`, `timestamp`. Per-file and per-peer arrays are dropped from the parquet (the projector does not handle repeated nested messages).",
        "tags": [
          "Torrents"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format (expected YYYY-MM-DD or 'latest')",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `TORRENTS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/torrents/export/{date}/meta": {
      "get": {
        "operationId": "getTorrentsExportMeta",
        "summary": "Get torrents parquet metadata",
        "description": "Metadata for a single **torrents** parquet snapshot: SHA-256 checksum, byte size, row count, parquet schema (column names + types), and canonical date. No file body — call the matching `/export/{date}` endpoint to download.\n\n**Required scope:** `TORRENTS_FEED`.",
        "tags": [
          "Torrents"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `TORRENTS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No export exists for the requested date. If the date's daily rollup is still being assembled (roughly 00:00–01:30 UTC for the largest streams), the response carries a Retry-After header and a problem detail saying so — the individual hours remain downloadable at /export/{date}/{hour} in the meantime.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/torrents/export/{date}/{hour}": {
      "get": {
        "operationId": "downloadTorrentsHourlyExport",
        "summary": "Download a torrents hourly parquet export",
        "description": "Download a specific hourly parquet snapshot for **torrents** by date and hour. Returns a 307 redirect to a presigned R2 URL valid for 24 hours.\n\nHourly files are transient — shortly after 00:30 UTC they are rolled up into the daily export and the per-hour artifacts are deleted. Hours remain downloadable until their day's rollup completes (including yesterday's hours in the window right after midnight UTC); once rolled up, requests return 400 with a note pointing at `/export/{date}` for the daily rollup. The `latest` alias rolls forward each hour and tracks the most recent hourly.\n\n**Required scope:** `TORRENTS_FEED`. **Columns:** `info_hash`, `name`, `magnet_uri`, `total_size`, `piece_length`, `file_count`, `timestamp`. Per-file and per-peer arrays are dropped from the parquet (the projector does not handle repeated nested messages).",
        "tags": [
          "Torrents"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "307": {
            "description": "Temporary redirect to a 24h presigned download URL on the CDN."
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or the hour has already been rolled into the daily export (request /export/{date} instead)",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `TORRENTS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/torrents/export/{date}/{hour}/meta": {
      "get": {
        "operationId": "getTorrentsHourlyExportMeta",
        "summary": "Get torrents hourly parquet metadata",
        "description": "Metadata for one hourly **torrents** parquet snapshot identified by date and hour. Same shape as the daily `/meta` response.\n\nHourly metadata is available while the hour's file exists — including yesterday's hours until their daily rollup completes. Once rolled up, request the daily `/export/{date}/meta` instead.\n\n**Required scope:** `TORRENTS_FEED`.",
        "tags": [
          "Torrents"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-05-03"
            },
            "description": "Either a YYYY-MM-DD date for a daily rollup, or the literal string `latest` for the most recent hourly export."
          },
          {
            "name": "hour",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 14,
              "minimum": 0,
              "maximum": 23
            },
            "description": "Hour of day (UTC) — integer 0..23. Identifies the specific hourly snapshot for `{date}`."
          }
        ],
        "responses": {
          "200": {
            "description": "Export-feed metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportMetaResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad date format, hour out of range (0..23), or date is not the current UTC date",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks `TORRENTS_FEED`",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No hourly export exists for the requested hour",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/feeds/torrents/stream": {
      "get": {
        "operationId": "streamTorrents",
        "summary": "Stream torrent DHT/tracker events in real time",
        "description": "Real-time NDJSON stream of torrent peer sightings from the BitTorrent DHT and public trackers. Each event carries the info hash, torrent metadata, and the peers observed announcing it.\n\n**Required scope:** `TORRENTS_STREAM`. **Schema:** see `TorrentStreamEntry` under Components.\n\n**Sample event:**\n```\n{\"info_hash\":\"c12fe1c06bba254a9dc9f519b335aa7c1367a88a\",\"name\":\"ubuntu-22.04.iso\",\"magnet_uri\":\"magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a\",\"total_size\":3826831360,\"piece_length\":262144,\"file_count\":1,\"timestamp\":1714780800}\n```",
        "tags": [
          "Torrents"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "NDJSON stream of torrents events. Each line is a complete JSON object. The connection stays open for up to 30 minutes.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "$ref": "#/components/schemas/TorrentStreamEntry"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid API key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key lacks the required scope for this feed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too many concurrent streams from this client",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable — streaming backend is down",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "healthCheck",
        "summary": "Health check",
        "description": "Liveness check for the API. Returns `ok` when all dependencies are reachable.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Service is unhealthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "problems": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "status": {
                      "type": "string",
                      "example": "unhealthy"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/lookup/domain/{domain}": {
      "get": {
        "operationId": "domainLookup",
        "summary": "Look up honeypot intelligence for a domain",
        "description": "Return honeypot intelligence for a domain: aggregate stats, a daily time series, unique-IP block, top ASN/subdomains/ports, geo distribution, an hour×day-of-week activity heatmap, derived activity stats, and the most recent events.",
        "tags": [
          "Lookup"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "google.com"
            },
            "description": "Domain name to look up. Encode internationalised names as Punycode (xn--…)."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 50
            },
            "description": "Maximum number of recent_events rows to return. Defaults to 50; values above 1000 are clamped server-side."
          }
        ],
        "responses": {
          "200": {
            "description": "Domain intelligence result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainLookupResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "402": {
            "description": "Payment Required - Quota exhausted",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Validation error (bad domain syntax, too long)",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable (lookup deadline exceeded or backend offline)",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/lookup/ip/{ip_address}": {
      "get": {
        "operationId": "ipLookup",
        "summary": "Look up providers for an IP address",
        "description": "Return the proxy, VPN, and anonymizer providers associated with a single IP address.",
        "tags": [
          "Lookup"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "ip_address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "8.8.8.8"
            },
            "description": "IPv4 or IPv6 address to look up"
          }
        ],
        "responses": {
          "200": {
            "description": "IP providers result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IpProvidersResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Validation error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/lookup/ips": {
      "post": {
        "operationId": "lookupIpBatch",
        "summary": "Enrich multiple IP addresses",
        "description": "Enrich up to 1,000 IP addresses in a single request. Billed at a 10% discount (ceil(n × 0.9) credits); duplicates and invalid IPs are excluded before charging.",
        "tags": [
          "Lookup"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "requestBody": {
          "description": "Batch IP lookup request",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IpBatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful batch lookup",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IpBatchResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "402": {
            "description": "Payment Required - Quota exhausted",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ASNEntry": {
        "type": "object",
        "properties": {
          "asn": {
            "type": "integer"
          },
          "events": {
            "type": "integer",
            "format": "int64"
          }
        },
        "required": [
          "asn",
          "events"
        ]
      },
      "AccountAPIKey": {
        "type": "object",
        "properties": {
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "created_at"
        ]
      },
      "AccountInfoResponse": {
        "type": "object",
        "properties": {
          "api_key": {
            "$ref": "#/components/schemas/AccountAPIKey"
          },
          "email": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "lookup_quota": {
            "$ref": "#/components/schemas/LookupQuota"
          },
          "organization": {
            "$ref": "#/components/schemas/Organization"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "first_name",
          "last_name",
          "email",
          "organization",
          "scopes",
          "lookup_quota"
        ]
      },
      "ActivityStatsBlock": {
        "type": "object",
        "properties": {
          "cadence": {
            "type": "string"
          },
          "median_per_hour": {
            "type": "integer",
            "format": "int64"
          },
          "p95_per_hour": {
            "type": "integer",
            "format": "int64"
          },
          "peak_hour": {
            "type": "integer"
          },
          "quiet_hour": {
            "type": "integer"
          }
        },
        "required": [
          "peak_hour",
          "quiet_hour",
          "median_per_hour",
          "p95_per_hour",
          "cadence"
        ]
      },
      "AnonymizerStreamEntry": {
        "type": "object",
        "description": "A single anonymizer event from the real-time anonymizer stream. Contains IP ranges rather than individual addresses.",
        "properties": {
          "asn": {
            "type": "integer",
            "format": "int32",
            "description": "Autonomous System Number of range_start"
          },
          "country_code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country code of range_start"
          },
          "provider": {
            "type": "string",
            "description": "Provider name"
          },
          "range_end": {
            "type": "string",
            "description": "End of the anonymizer IP range"
          },
          "range_start": {
            "type": "string",
            "description": "Start of the anonymizer IP range"
          },
          "timestamp": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp (seconds since epoch, UTC)"
          },
          "type": {
            "type": "string",
            "description": "Proxy/anonymizer type"
          }
        }
      },
      "CountryEntry": {
        "type": "object",
        "properties": {
          "country_code": {
            "type": "string"
          },
          "events": {
            "type": "integer",
            "format": "int64"
          },
          "unique_ips": {
            "type": "integer",
            "format": "int64"
          }
        },
        "required": [
          "country_code",
          "unique_ips",
          "events"
        ]
      },
      "DeviceInfo": {
        "type": "object",
        "properties": {
          "last_seen": {
            "type": "integer",
            "format": "int64"
          },
          "os": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        },
        "required": [
          "os",
          "version",
          "last_seen"
        ]
      },
      "DomainLookupResponse": {
        "type": "object",
        "properties": {
          "activity_stats": {
            "$ref": "#/components/schemas/ActivityStatsBlock"
          },
          "domain": {
            "type": "string"
          },
          "geo_distribution": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CountryEntry"
            }
          },
          "hour_dow_heatmap": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "integer",
                "format": "int64"
              }
            }
          },
          "recent_events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecentEvent"
            }
          },
          "stats": {
            "$ref": "#/components/schemas/Stats"
          },
          "status": {
            "type": "string"
          },
          "time_series": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TimeSeriesEntry"
            }
          },
          "top_asn": {
            "$ref": "#/components/schemas/ASNEntry"
          },
          "top_ports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortEntry"
            }
          },
          "top_subdomains": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubdomainEntry"
            }
          },
          "unique_ips": {
            "$ref": "#/components/schemas/UniqueIPsBlock"
          }
        },
        "required": [
          "domain",
          "status",
          "stats",
          "time_series",
          "unique_ips",
          "top_subdomains",
          "top_ports",
          "geo_distribution",
          "hour_dow_heatmap",
          "activity_stats",
          "recent_events"
        ]
      },
      "ExportListItem": {
        "type": "object",
        "properties": {
          "checksum": {
            "type": "string"
          },
          "created_at": {
            "type": "integer",
            "format": "int64"
          },
          "date": {
            "type": "string"
          },
          "download_path": {
            "type": "string"
          },
          "hour": {
            "type": "integer"
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "row_count": {
            "type": "integer",
            "format": "int64"
          },
          "size_bytes": {
            "type": "integer",
            "format": "int64"
          }
        },
        "required": [
          "kind",
          "date",
          "size_bytes",
          "row_count",
          "checksum",
          "id",
          "created_at",
          "download_path"
        ]
      },
      "ExportListResponse": {
        "type": "object",
        "properties": {
          "feeds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportListItem"
            }
          },
          "next_cursor": {
            "type": "string"
          },
          "stream": {
            "type": "string"
          }
        },
        "required": [
          "stream",
          "feeds"
        ]
      },
      "ExportMetaResponse": {
        "type": "object",
        "properties": {
          "checksum": {
            "type": "string"
          },
          "created_at": {
            "type": "integer",
            "format": "int64"
          },
          "date": {
            "type": "integer",
            "format": "int64"
          },
          "format": {
            "type": "string"
          },
          "hour": {
            "type": "integer"
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "rows": {
            "type": "integer",
            "format": "int64"
          },
          "schema": {
            "$ref": "#/components/schemas/Schema"
          },
          "size": {
            "type": "integer",
            "format": "int64"
          },
          "stream": {
            "type": "string"
          }
        },
        "required": [
          "checksum",
          "date",
          "format",
          "size",
          "rows",
          "schema",
          "stream",
          "kind",
          "id",
          "created_at"
        ]
      },
      "HoneypotADBStreamEntry": {
        "type": "object",
        "properties": {
          "command": {
            "type": "string",
            "format": "byte",
            "description": "Shell command executed (base64-encoded bytes)"
          },
          "hash": {
            "type": "string",
            "description": "Command hash"
          },
          "sequential_id": {
            "type": "integer"
          },
          "session": {
            "type": "string",
            "description": "ADB session hash"
          }
        }
      },
      "HoneypotDNSStreamEntry": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string",
            "description": "DNS name the attacker asked the honeypot to resolve"
          },
          "meta": {
            "$ref": "#/components/schemas/HoneypotMeta"
          },
          "port": {
            "type": "integer",
            "description": "Destination port (53, 853, 5353)"
          },
          "timestamp": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp (seconds since epoch, UTC)"
          },
          "tunnel_id": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "HoneypotHTTPSStreamEntry": {
        "type": "object",
        "properties": {
          "details": {
            "type": "string",
            "description": "TLS ClientHello details as a JSON-encoded string (cipher suites, extensions, supported curves, ALPN)"
          },
          "domain": {
            "type": "string"
          },
          "meta": {
            "$ref": "#/components/schemas/HoneypotMeta"
          },
          "port": {
            "type": "integer"
          },
          "raw": {
            "type": "string",
            "format": "byte",
            "description": "Base64-encoded raw ClientHello bytes; omitted when not captured."
          },
          "timestamp": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp (seconds since epoch, UTC)"
          },
          "tunnel_id": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "HoneypotHTTPStreamEntry": {
        "type": "object",
        "properties": {
          "details": {
            "type": "object",
            "properties": {
              "headers": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "method": {
                "type": "string"
              },
              "uri": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "domain": {
            "type": "string"
          },
          "meta": {
            "$ref": "#/components/schemas/HoneypotMeta"
          },
          "port": {
            "type": "integer"
          },
          "raw": {
            "type": "string"
          },
          "timestamp": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp (seconds since epoch, UTC)"
          },
          "tunnel_id": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "HoneypotMeta": {
        "type": "object",
        "properties": {
          "pool_id": {
            "type": "string",
            "description": "Pool identifier"
          },
          "provider": {
            "type": "string",
            "description": "Provider name"
          },
          "proxy_ip": {
            "type": "string",
            "description": "Source proxy IP address"
          },
          "server": {
            "type": "string",
            "description": "Honeypot server identifier"
          }
        }
      },
      "IntelligenceInfo": {
        "type": "object",
        "properties": {
          "behavior": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "devices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeviceInfo"
            }
          },
          "providers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProviderEntry"
            }
          },
          "risk_score": {
            "type": "integer"
          }
        },
        "required": [
          "risk_score",
          "devices",
          "behavior",
          "categories",
          "providers"
        ]
      },
      "IpBatchRequest": {
        "type": "object",
        "properties": {
          "ips": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "ips"
        ]
      },
      "IpBatchResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IpProvidersResponse"
            }
          }
        },
        "required": [
          "results"
        ]
      },
      "IpProvidersResponse": {
        "type": "object",
        "properties": {
          "intelligence": {
            "$ref": "#/components/schemas/IntelligenceInfo"
          },
          "ip": {
            "type": "string"
          },
          "location": {
            "$ref": "#/components/schemas/LocationInfo"
          },
          "network": {
            "$ref": "#/components/schemas/NetworkInfo"
          }
        },
        "required": [
          "ip"
        ]
      },
      "JA3StreamEntry": {
        "type": "object",
        "properties": {
          "address": {
            "type": "string",
            "description": "IPv4 or IPv6 address"
          },
          "host": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "ja3n": {
            "type": "string",
            "description": "JA3N fingerprint hash"
          },
          "ja4": {
            "type": "string",
            "description": "JA4 fingerprint hash"
          },
          "ja4h": {
            "type": "string",
            "description": "JA4H fingerprint hash"
          },
          "protocol": {
            "type": "string"
          },
          "secure": {
            "type": "boolean"
          },
          "timestamp": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp (seconds since epoch, UTC)"
          },
          "user_agent": {
            "type": "string"
          }
        }
      },
      "JA4StreamEntry": {
        "type": "object",
        "properties": {
          "address": {
            "type": "string",
            "description": "IPv4 or IPv6 address"
          },
          "ch_server_name": {
            "type": "string",
            "description": "TLS SNI server name"
          },
          "event_id": {
            "type": "string",
            "description": "Unique event identifier"
          },
          "ja4": {
            "type": "string",
            "description": "JA4 TLS fingerprint hash"
          },
          "ja4_r": {
            "type": "string",
            "description": "JA4 raw fingerprint"
          },
          "ja4h": {
            "type": "string",
            "description": "JA4H HTTP fingerprint hash"
          },
          "ja4h_r": {
            "type": "string",
            "description": "JA4H raw fingerprint"
          },
          "ja4l": {
            "type": "string",
            "description": "JA4L latency fingerprint"
          },
          "ja4t": {
            "type": "string",
            "description": "JA4T TCP fingerprint"
          },
          "timestamp": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp (seconds since epoch, UTC)"
          },
          "url_path": {
            "type": "string"
          }
        }
      },
      "JA4TStreamEntry": {
        "type": "object",
        "properties": {
          "asn": {
            "type": "integer",
            "format": "int32",
            "description": "Autonomous System Number of the IP"
          },
          "country_code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country code of the IP"
          },
          "ip": {
            "type": "string",
            "description": "IPv4 or IPv6 address of the sighting"
          },
          "ja4t": {
            "type": "string",
            "description": "JA4T TCP fingerprint"
          },
          "provider": {
            "type": "string",
            "description": "Provider that surfaced this sighting"
          },
          "timestamp": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp (seconds since epoch, UTC)"
          },
          "type": {
            "type": "string",
            "description": "ProxyType category (e.g. DATACENTER_PROXY, RESIDENTIAL_PROXY)"
          }
        }
      },
      "LocationInfo": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "geo_hash": {
            "type": "string"
          },
          "latitude": {
            "type": "number"
          },
          "longitude": {
            "type": "number"
          },
          "state": {
            "type": "string"
          },
          "timezone": {
            "type": "string"
          }
        },
        "required": [
          "country",
          "state",
          "city",
          "timezone",
          "longitude",
          "latitude",
          "geo_hash"
        ]
      },
      "LookupQuota": {
        "type": "object",
        "properties": {
          "credits": {
            "type": "integer",
            "format": "int64"
          },
          "resets_in": {
            "type": "integer",
            "format": "int64"
          }
        },
        "required": [
          "credits",
          "resets_in"
        ]
      },
      "NetworkInfo": {
        "type": "object",
        "properties": {
          "abuse_email": {
            "type": "string"
          },
          "abuse_phone": {
            "type": "string"
          },
          "asn": {
            "type": "integer"
          },
          "domain": {
            "type": "string"
          },
          "isp": {
            "type": "string"
          },
          "org": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "asn",
          "isp",
          "type"
        ]
      },
      "Organization": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "relation": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "relation"
        ]
      },
      "PortEntry": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer",
            "format": "int64"
          },
          "port": {
            "type": "integer"
          }
        },
        "required": [
          "port",
          "count"
        ]
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ]
      },
      "ProjectedColumn": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "type"
        ]
      },
      "ProviderEntry": {
        "type": "object",
        "properties": {
          "last_seen": {
            "type": "integer",
            "format": "int64"
          },
          "provider": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "provider",
          "type",
          "last_seen"
        ]
      },
      "ProxyStreamEntry": {
        "type": "object",
        "description": "A single proxy event from the real-time proxy stream.",
        "properties": {
          "asn": {
            "type": "integer",
            "format": "int32",
            "description": "Autonomous System Number of the proxy IP"
          },
          "country_code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country code of the proxy IP"
          },
          "ip": {
            "type": "string",
            "description": "IPv4 or IPv6 address of the proxy"
          },
          "provider": {
            "type": "string",
            "description": "Provider name"
          },
          "timestamp": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp (seconds since epoch, UTC)"
          },
          "type": {
            "type": "string",
            "description": "Proxy type (e.g. residential, datacenter, mobile)"
          }
        }
      },
      "RecentEvent": {
        "type": "object",
        "properties": {
          "country_code": {
            "type": "string"
          },
          "port": {
            "type": "integer"
          },
          "source_ip_masked": {
            "type": "string"
          },
          "target_subdomain": {
            "type": "string"
          },
          "timestamp": {
            "type": "integer",
            "format": "int64"
          }
        },
        "required": [
          "timestamp",
          "source_ip_masked",
          "target_subdomain",
          "port",
          "country_code"
        ]
      },
      "Schema": {
        "type": "object",
        "properties": {
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectedColumn"
            }
          }
        },
        "required": [
          "fields"
        ]
      },
      "Stats": {
        "type": "object",
        "properties": {
          "events_24h": {
            "type": "integer",
            "format": "int64"
          },
          "total_events_30d": {
            "type": "integer",
            "format": "int64"
          }
        },
        "required": [
          "total_events_30d",
          "events_24h"
        ]
      },
      "SubdomainEntry": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer",
            "format": "int64"
          },
          "subdomain": {
            "type": "string"
          }
        },
        "required": [
          "subdomain",
          "count"
        ]
      },
      "TimeSeriesEntry": {
        "type": "object",
        "properties": {
          "date": {
            "type": "integer",
            "format": "int64"
          },
          "events": {
            "type": "integer",
            "format": "int64"
          },
          "unique_ips": {
            "type": "integer",
            "format": "int64"
          }
        },
        "required": [
          "date",
          "events",
          "unique_ips"
        ]
      },
      "TorrentFileEntry": {
        "type": "object",
        "properties": {
          "length": {
            "type": "integer",
            "format": "int64"
          },
          "path": {
            "type": "string"
          }
        }
      },
      "TorrentPeerEntry": {
        "type": "object",
        "properties": {
          "encrypted": {
            "type": "boolean"
          },
          "ip": {
            "type": "string",
            "description": "IPv4 or IPv6 address of the peer"
          },
          "port": {
            "type": "integer"
          },
          "source": {
            "type": "string",
            "description": "DHT, PEX, or tracker"
          }
        }
      },
      "TorrentStreamEntry": {
        "type": "object",
        "properties": {
          "file_count": {
            "type": "integer"
          },
          "files": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TorrentFileEntry"
            }
          },
          "info_hash": {
            "type": "string",
            "description": "40-character hex SHA-1 info hash"
          },
          "magnet_uri": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Torrent name from metadata"
          },
          "peers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TorrentPeerEntry"
            }
          },
          "piece_length": {
            "type": "integer"
          },
          "timestamp": {
            "type": "integer",
            "format": "int64",
            "description": "Unix timestamp (seconds since epoch, UTC)"
          },
          "total_size": {
            "type": "integer",
            "format": "int64",
            "description": "Total size of files in bytes"
          }
        }
      },
      "UniqueIPsBlock": {
        "type": "object",
        "properties": {
          "sparkline_24h": {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int64"
            }
          },
          "value_24h": {
            "type": "integer",
            "format": "int64"
          },
          "value_30d": {
            "type": "integer",
            "format": "int64"
          }
        },
        "required": [
          "value_24h",
          "value_30d",
          "sparkline_24h"
        ]
      },
      "ValidationProblemDetails": {
        "type": "object",
        "properties": {
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
      },
          "instance": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "errors"
        ]
      }
    },
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Private API key passed in the X-Api-Key header."
      }
    }
  }
}