{
  "openapi": "3.1.0",
  "info": {
    "title": "ThreadSnoop Reddit Read API",
    "version": "1.0.0",
    "description": "Read-only access to Reddit: posts, comments, comment trees, subreddit search, subreddit rules, and user profiles. Pay-as-you-go, 1 credit = 1 request, $0.50 (333 credits) free on signup. No writes, votes, DMs, or auth-ed Reddit actions — read-only, permanently. See https://threadsnoop.com/docs for the human-readable guide.",
    "contact": { "email": "hello@threadsnoop.com" }
  },
  "servers": [{ "url": "https://api.threadsnoop.com" }],
  "security": [{ "ApiKeyHeader": [] }, { "BearerAuth": [] }],
  "paths": {
    "/v1/posts": {
      "get": {
        "operationId": "listPosts",
        "summary": "List or fetch Reddit posts",
        "description": "Two modes on one endpoint. Listing mode: pass `subreddit` and/or `author` (at least one required) to page through posts, newest-first by default. Batch mode: pass `ids` (comma-separated post ids, capped at 100) to fetch specific posts by id in a single request — all other listing params (`after`, `before`, `sort`, `q`) are ignored in this mode, and the response has `cursor: null` / `has_more: false` since there is no next page for a fixed id list. 1 credit per request in either mode (batch mode is 1 upstream call regardless of how many ids you ask for).",
        "parameters": [
          { "$ref": "#/components/parameters/Subreddit" },
          { "$ref": "#/components/parameters/Author" },
          {
            "name": "ids",
            "in": "query",
            "description": "Comma-separated post ids to fetch directly (batch mode). Truncated to the first 100 if more are given. When present, `subreddit`/`author`/`after`/`before`/`sort`/`q` are ignored.",
            "schema": { "type": "string", "example": "1abcxyz,1defghi" }
          },
          { "$ref": "#/components/parameters/After" },
          { "$ref": "#/components/parameters/Before" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Sort" },
          { "$ref": "#/components/parameters/Fields" },
          { "$ref": "#/components/parameters/Q" }
        ],
        "responses": {
          "200": {
            "description": "A page of posts.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PostListEnvelope" },
                "example": {
                  "data": [
                    {
                      "id": "1abcxyz",
                      "title": "Anyone know a tool that watches Reddit for me?",
                      "selftext": "Tired of manually searching every day...",
                      "author": "some_founder",
                      "created_utc": 1753000000,
                      "subreddit": "SaaS",
                      "permalink": "/r/SaaS/comments/1abcxyz/anyone_know_a_tool_that_watches_reddit_for_me/",
                      "url": "https://www.reddit.com/r/SaaS/comments/1abcxyz/anyone_know_a_tool_that_watches_reddit_for_me/",
                      "num_comments": 4,
                      "score": 12,
                      "hide_score": false
                    }
                  ],
                  "_threadsnoop": { "cursor": 1753000000, "has_more": true, "credits_used": 1, "credits_remaining": 332 }
                }
              }
            },
            "headers": {
              "x-credits-used": { "$ref": "#/components/headers/CreditsUsed" },
              "x-credits-remaining": { "$ref": "#/components/headers/CreditsRemaining" }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamUnavailable" }
        }
      }
    },
    "/v1/comments": {
      "get": {
        "operationId": "listComments",
        "summary": "List or fetch Reddit comments",
        "description": "Two modes on one endpoint, mirroring /v1/posts. Listing mode: pass `subreddit` and/or `author` (at least one required) to page through comments, newest-first by default. Batch mode: pass `ids` (comma-separated comment ids, capped at 100) to fetch specific comments by id in a single request — all other listing params (`after`, `before`, `sort`, `q`) are ignored in this mode, and the response has `cursor: null` / `has_more: false` since there is no next page for a fixed id list. 1 credit per request in either mode.",
        "parameters": [
          { "$ref": "#/components/parameters/Subreddit" },
          { "$ref": "#/components/parameters/Author" },
          {
            "name": "ids",
            "in": "query",
            "description": "Comma-separated comment ids to fetch directly (batch mode). Truncated to the first 100 if more are given. When present, `subreddit`/`author`/`after`/`before`/`sort`/`q` are ignored.",
            "schema": { "type": "string", "example": "kxyzabc,kdefghi" }
          },
          { "$ref": "#/components/parameters/After" },
          { "$ref": "#/components/parameters/Before" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Sort" },
          { "$ref": "#/components/parameters/Fields" },
          { "$ref": "#/components/parameters/Q" }
        ],
        "responses": {
          "200": {
            "description": "A page of comments.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CommentListEnvelope" },
                "example": {
                  "data": [
                    {
                      "id": "kxyzabc",
                      "body": "I've been looking for exactly this, what did you end up using?",
                      "author": "another_user",
                      "created_utc": 1753001200,
                      "subreddit": "SaaS",
                      "link_id": "t3_1abcxyz",
                      "parent_id": "t3_1abcxyz",
                      "permalink": "/r/SaaS/comments/1abcxyz/anyone_know_a_tool_that_watches_reddit_for_me/kxyzabc/"
                    }
                  ],
                  "_threadsnoop": { "cursor": 1753001200, "has_more": true, "credits_used": 1, "credits_remaining": 331 }
                }
              }
            },
            "headers": {
              "x-credits-used": { "$ref": "#/components/headers/CreditsUsed" },
              "x-credits-remaining": { "$ref": "#/components/headers/CreditsRemaining" }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamUnavailable" }
        }
      }
    },
    "/v1/comments/tree": {
      "get": {
        "operationId": "getCommentTree",
        "summary": "Get the full comment tree for a post",
        "description": "Returns the complete nested comment tree for a post in one call, including a real comment count that is accurate even during Reddit's ~36-hour hide_score blackout window (when the post's own num_comments reads 0). Not paginated — cursor/has_more are always null/false since one call returns everything.",
        "parameters": [
          {
            "name": "post_id",
            "in": "query",
            "required": true,
            "description": "Post id, with or without the `t3_` prefix.",
            "schema": { "type": "string", "example": "1abcxyz" }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Max comments to return.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 50 }
          }
        ],
        "responses": {
          "200": {
            "description": "The post's comment tree.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CommentTreeEnvelope" },
                "example": {
                  "data": [
                    {
                      "kind": "t1",
                      "data": {
                        "id": "kxyzabc",
                        "body": "I've been looking for exactly this",
                        "author": "another_user",
                        "created_utc": 1753001200,
                        "subreddit": "SaaS",
                        "link_id": "t3_1abcxyz",
                        "parent_id": "t3_1abcxyz",
                        "score": 3
                      },
                      "replies": []
                    }
                  ],
                  "_threadsnoop": { "cursor": null, "has_more": false, "credits_used": 1, "credits_remaining": 330 }
                }
              }
            },
            "headers": {
              "x-credits-used": { "$ref": "#/components/headers/CreditsUsed" },
              "x-credits-remaining": { "$ref": "#/components/headers/CreditsRemaining" }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamUnavailable" }
        }
      }
    },
    "/v1/subreddits/search": {
      "get": {
        "operationId": "searchSubreddits",
        "summary": "Search subreddits by name",
        "description": "Name-prefix search over subreddit metadata (subscriber count, description). Returns up to 10 matches; there is no customer-facing `limit` param and no pagination (cursor/has_more are always null/false).",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Subreddit name or name prefix to search for.",
            "schema": { "type": "string", "example": "saas" }
          }
        ],
        "responses": {
          "200": {
            "description": "Up to 10 matching subreddits.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubredditSearchEnvelope" },
                "example": {
                  "data": [
                    { "name": "SaaS", "subscribers": 180000, "description": "A place for SaaS founders..." }
                  ],
                  "_threadsnoop": { "cursor": null, "has_more": false, "credits_used": 1, "credits_remaining": 329 }
                }
              }
            },
            "headers": {
              "x-credits-used": { "$ref": "#/components/headers/CreditsUsed" },
              "x-credits-remaining": { "$ref": "#/components/headers/CreditsRemaining" }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamUnavailable" }
        }
      }
    },
    "/v1/subreddits/{name}/rules": {
      "get": {
        "operationId": "getSubredditRules",
        "summary": "Get a subreddit's rules",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "Subreddit name, 3-21 characters, letters/digits/underscore only.",
            "schema": { "type": "string", "pattern": "^[A-Za-z0-9_]{3,21}$", "example": "SaaS" }
          }
        ],
        "responses": {
          "200": {
            "description": "The subreddit's rules.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubredditRulesEnvelope" },
                "example": {
                  "data": {
                    "subreddit": "SaaS",
                    "rules": [
                      { "short_name": "No self-promotion", "description": "Do not post ads or referral links.", "priority": 0 }
                    ]
                  },
                  "_threadsnoop": { "credits_used": 1, "credits_remaining": 328 }
                }
              }
            },
            "headers": {
              "x-credits-used": { "$ref": "#/components/headers/CreditsUsed" },
              "x-credits-remaining": { "$ref": "#/components/headers/CreditsRemaining" }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamUnavailable" }
        }
      }
    },
    "/v1/users/{name}": {
      "get": {
        "operationId": "getUserProfile",
        "summary": "Get a Reddit user's profile aggregate",
        "description": "Single-user karma/post/comment aggregate plus first/last activity timestamps, via an exact username match (not prefix/fuzzy search) — roughly Reddit's own `/user/{name}/about`. A username with no matching Reddit account is still 1 credit (the lookup made a real upstream call either way) and comes back as a normal 200 with `data.user: null`, not a 404 — there is no null-data/404 branch in this API's response pipeline, so a miss and a hit share the same envelope shape.",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "Reddit username, 3-64 characters, letters/digits/underscore/hyphen only (a generous charset guard, not Reddit's exact 3-20-char registration rule).",
            "schema": { "type": "string", "pattern": "^[A-Za-z0-9_-]{3,64}$", "example": "spez" }
          }
        ],
        "responses": {
          "200": {
            "description": "The user's profile aggregate, or null if no such user exists. Charged 1 credit either way.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UserProfileEnvelope" },
                "example": {
                  "data": {
                    "user": {
                      "author": "spez",
                      "id": "1w72",
                      "_meta": {
                        "num_posts": 549,
                        "num_comments": 2568,
                        "post_karma": 832984,
                        "comment_karma": 666948,
                        "total_karma": 1499932,
                        "earliest_post_at": 1119552314,
                        "earliest_comment_at": 1134392748,
                        "last_post_at": 1751475161,
                        "last_comment_at": 1729645028
                      }
                    }
                  },
                  "_threadsnoop": { "credits_used": 1, "credits_remaining": 327 }
                }
              }
            },
            "headers": {
              "x-credits-used": { "$ref": "#/components/headers/CreditsUsed" },
              "x-credits-remaining": { "$ref": "#/components/headers/CreditsRemaining" }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamUnavailable" }
        }
      }
    },
    "/v1/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "Check your credit balance and rate limit",
        "description": "Free — does not consume a credit and does not make an upstream request. Still key-authed and rate-limited like every other endpoint. Reads the balance fresh from the database (not the ~30s auth cache other endpoints may use), so it's always current.",
        "responses": {
          "200": {
            "description": "Current balance and rate limit.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AccountEnvelope" },
                "example": {
                  "data": { "credits_balance": 328, "rate_limit": { "qps": 2, "burst": 10 } },
                  "_threadsnoop": { "credits_used": 0, "credits_remaining": 328 }
                }
              }
            },
            "headers": {
              "x-credits-used": { "$ref": "#/components/headers/CreditsUsed" },
              "x-credits-remaining": { "$ref": "#/components/headers/CreditsRemaining" }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": { "type": "apiKey", "in": "header", "name": "x-api-key" },
      "BearerAuth": { "type": "http", "scheme": "bearer", "description": "Authorization: Bearer <your key>. Equivalent to x-api-key; either header works." }
    },
    "parameters": {
      "Subreddit": {
        "name": "subreddit",
        "in": "query",
        "description": "Subreddit name (without r/). At least one of subreddit/author/ids is required.",
        "schema": { "type": "string", "example": "SaaS" }
      },
      "Author": {
        "name": "author",
        "in": "query",
        "description": "Reddit username (without u/). At least one of subreddit/author/ids is required.",
        "schema": { "type": "string" }
      },
      "After": {
        "name": "after",
        "in": "query",
        "description": "Lower bound on created_utc. Accepts an epoch-seconds integer or a relative form like `7d`, passed through as-is.",
        "schema": { "type": "string", "example": "7d" }
      },
      "Before": {
        "name": "before",
        "in": "query",
        "description": "Upper bound on created_utc. Same accepted forms as `after`.",
        "schema": { "type": "string" }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Page size.",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 }
      },
      "Sort": {
        "name": "sort",
        "in": "query",
        "description": "Sort order by created_utc.",
        "schema": { "type": "string", "enum": ["asc", "desc"], "default": "desc" }
      },
      "Fields": {
        "name": "fields",
        "in": "query",
        "description": "Comma-separated field allowlist forwarded upstream, e.g. `id,title,author,created_utc`. Omit to get the full untrimmed object.",
        "schema": { "type": "string" }
      },
      "Q": {
        "name": "q",
        "in": "query",
        "description": "Client-side exact-phrase filter (with basic plural flexibility) applied to the fetched page before it's returned. Every page fetched still costs 1 credit whether it matched or not, and cursor/has_more always reflect the RAW page, not the filtered subset — a page that comes back empty after filtering does not mean there are no more results, keep paginating. See the pagination guide.",
        "schema": { "type": "string" }
      }
    },
    "headers": {
      "CreditsUsed": {
        "description": "Credits charged for this request (0 or 1).",
        "schema": { "type": "integer" }
      },
      "CreditsRemaining": {
        "description": "Balance remaining after this request.",
        "schema": { "type": "integer" }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid or missing parameters. Not charged.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "at least one of subreddit, author, or ids is required" } } }
      },
      "Unauthorized": {
        "description": "Missing, invalid, or revoked API key. Not charged (no key to attribute usage to).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "invalid or revoked api key" } } }
      },
      "PaymentRequired": {
        "description": "Insufficient credit balance. Not charged (the debit is the balance check — it never went through).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "insufficient credits — top up at https://threadsnoop.com/api" } } }
      },
      "RateLimited": {
        "description": "Rate limit exceeded — either your key's own 2 req/s (burst 10) limit, or the shared global upstream ceiling (marked by the `x-ratelimit-scope: global` header, present only for the global case). Not charged.",
        "headers": {
          "Retry-After": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer" } },
          "x-ratelimit-scope": { "description": "Present and set to `global` only when the shared upstream ceiling (not your per-key limit) was hit.", "schema": { "type": "string", "enum": ["global"] } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "rate limit exceeded" } } }
      },
      "UpstreamUnavailable": {
        "description": "The upstream data source was unreachable or errored after the connector's retry policy was exhausted. Any credit debited for this request is automatically refunded.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "upstream temporarily unavailable — credit refunded" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": { "error": { "type": "string" } }
      },
      "Post": {
        "type": "object",
        "description": "Full Reddit post object, untrimmed unless you pass `fields=`. The properties below are the ones ThreadSnoop's connector reads directly; additional standard Reddit post fields pass through unmodified.",
        "properties": {
          "id": { "type": "string" },
          "title": { "type": "string" },
          "selftext": { "type": "string" },
          "author": { "type": "string" },
          "created_utc": { "type": "integer" },
          "subreddit": { "type": "string" },
          "permalink": { "type": "string", "description": "Relative path on reddit.com. Prepend https://www.reddit.com for a clickable URL." },
          "url": { "type": "string", "description": "Absolute URL — the external link for a link post, or the same permalink (fully qualified) for a self post." },
          "num_comments": { "type": "integer", "description": "0 during the ~36h hide_score blackout on fresh posts — use /v1/comments/tree for a real count." },
          "score": { "type": "integer", "description": "Pinned at 1 during the ~36h hide_score blackout on fresh posts." },
          "hide_score": { "type": "boolean" }
        },
        "additionalProperties": true
      },
      "Comment": {
        "type": "object",
        "description": "Full Reddit comment object, untrimmed unless you pass `fields=`.",
        "properties": {
          "id": { "type": "string" },
          "body": { "type": "string" },
          "author": { "type": "string" },
          "created_utc": { "type": "integer" },
          "subreddit": { "type": "string" },
          "link_id": { "type": "string", "description": "Fullname (t3_...) of the parent post." },
          "parent_id": { "type": "string", "description": "Fullname of the immediate parent (post or comment)." },
          "permalink": { "type": "string", "description": "Relative path on reddit.com. Prepend https://www.reddit.com for a clickable URL." }
        },
        "additionalProperties": true
      },
      "CommentTreeNode": {
        "type": "object",
        "properties": {
          "kind": { "type": "string" },
          "data": {
            "allOf": [
              { "$ref": "#/components/schemas/Comment" },
              { "type": "object", "properties": { "score": { "type": "integer" } } }
            ]
          },
          "replies": { "type": "array", "items": { "$ref": "#/components/schemas/CommentTreeNode" } }
        }
      },
      "SubredditMeta": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "subscribers": { "type": "integer" },
          "description": { "type": "string" }
        }
      },
      "SubredditRule": {
        "type": "object",
        "properties": {
          "short_name": { "type": "string" },
          "description": { "type": "string" },
          "priority": { "type": "integer" }
        }
      },
      "UserProfile": {
        "type": "object",
        "description": "Single-user karma/post/comment aggregate, matched by exact username.",
        "properties": {
          "author": { "type": "string" },
          "id": { "type": "string" },
          "_meta": {
            "type": "object",
            "properties": {
              "num_posts": { "type": "integer" },
              "num_comments": { "type": "integer" },
              "post_karma": { "type": "integer" },
              "comment_karma": { "type": "integer" },
              "total_karma": { "type": "integer" },
              "earliest_post_at": { "type": "integer" },
              "earliest_comment_at": { "type": "integer" },
              "last_post_at": { "type": "integer" },
              "last_comment_at": { "type": "integer" }
            }
          }
        },
        "additionalProperties": true
      },
      "Envelope": {
        "type": "object",
        "properties": {
          "cursor": { "type": ["integer", "null"], "description": "created_utc of the last row in this page (raw page, pre-filter if q= was used). Pass as `after` (sort=asc) or `before` (sort=desc) to fetch the next page. Null when the page was empty or the endpoint isn't paginated." },
          "has_more": { "type": "boolean" },
          "credits_used": { "type": "integer" },
          "credits_remaining": { "type": "integer" }
        },
        "required": ["credits_used", "credits_remaining"]
      },
      "PostListEnvelope": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Post" } },
          "_threadsnoop": { "$ref": "#/components/schemas/Envelope" }
        }
      },
      "CommentListEnvelope": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Comment" } },
          "_threadsnoop": { "$ref": "#/components/schemas/Envelope" }
        }
      },
      "CommentTreeEnvelope": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/CommentTreeNode" } },
          "_threadsnoop": { "$ref": "#/components/schemas/Envelope" }
        }
      },
      "SubredditSearchEnvelope": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/SubredditMeta" } },
          "_threadsnoop": { "$ref": "#/components/schemas/Envelope" }
        }
      },
      "SubredditRulesEnvelope": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "subreddit": { "type": "string" },
              "rules": { "type": "array", "items": { "$ref": "#/components/schemas/SubredditRule" } }
            }
          },
          "_threadsnoop": { "$ref": "#/components/schemas/Envelope" }
        }
      },
      "UserProfileEnvelope": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "user": {
                "anyOf": [{ "$ref": "#/components/schemas/UserProfile" }, { "type": "null" }],
                "description": "The user's profile, or null when no matching Reddit account exists."
              }
            }
          },
          "_threadsnoop": { "$ref": "#/components/schemas/Envelope" }
        }
      },
      "AccountEnvelope": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "credits_balance": { "type": "integer" },
              "rate_limit": {
                "type": "object",
                "properties": { "qps": { "type": "integer" }, "burst": { "type": "integer" } }
              }
            }
          },
          "_threadsnoop": { "$ref": "#/components/schemas/Envelope" }
        }
      }
    }
  }
}
