{
  "openapi": "3.1.0",
  "info": {
    "title": "ASKGIB public API",
    "version": "1.0.0",
    "summary": "Articles, city search and reader submissions for askgib.com.",
    "description": "The API behind ASKGIB, the publication of Gibril Bachouchi. Reads need no authentication, no key and no signup. Writes are the site's own forms: same-origin browser requests only, rate-limited, optional captcha.\n\nRate limits: Every /api response carries RateLimit-Policy, the list of quotas this API enforces (draft-ietf-httpapi-ratelimit-headers structured fields). The four write routes add RateLimit (combined), RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset for the quota that request used. Reads consume no quota, so they carry the policy list only. A 429 adds Retry-After in seconds. The windows are sliding, so RateLimit-Reset states the window length: the worst case before the quota is free again.\n\nVersioning: The API follows semantic versioning and is stable. The live version is 1.0.0: it is info.version in /openapi.json and the API-Version header on every /api response. The paths carry no version prefix: the unversioned paths are version 1 and stay version 1. An additive change (a new endpoint, a new optional field or parameter) raises the minor or patch number and breaks no client. A breaking change is never made in place: it ships under a new path prefix (/api/v2/...), and the version 1 operation it replaces is first marked deprecated: true in /openapi.json and answers with the Deprecation (RFC 9745) and Sunset (RFC 8594) response headers for at least 90 days before it is removed. Every change is written up on /developers.\n\nDocs: https://www.askgib.com/developers. Guide for agents: https://www.askgib.com/agents.md.",
    "contact": {
      "name": "Gibril Bachouchi",
      "url": "https://www.askgib.com/developers"
    }
  },
  "x-api-policy": {
    "version": "1.0.0",
    "versioning": "semver",
    "stability": "stable",
    "versionHeader": "API-Version",
    "pathVersioning": "The unversioned paths are version 1. A breaking change ships under a new path prefix (/api/v2/...), never in place.",
    "compatibility": "Additive changes (new endpoints, new optional fields or parameters) are not breaking and keep the major version.",
    "deprecation": {
      "operationFlag": "deprecated",
      "headers": [
        "Deprecation",
        "Sunset"
      ],
      "specifications": [
        "https://www.rfc-editor.org/rfc/rfc9745",
        "https://www.rfc-editor.org/rfc/rfc8594"
      ],
      "minimumNoticeDays": 90
    },
    "deprecatedOperations": [],
    "changelog": "https://www.askgib.com/developers#versioning"
  },
  "servers": [
    {
      "url": "https://www.askgib.com",
      "description": "Version 1, stable. The paths carry no version prefix."
    }
  ],
  "externalDocs": {
    "description": "Developer docs",
    "url": "https://www.askgib.com/developers"
  },
  "security": [],
  "tags": [
    {
      "name": "articles",
      "description": "Read the published articles."
    },
    {
      "name": "beer-index",
      "description": "Beer Index city lookup and submissions."
    },
    {
      "name": "reader-input",
      "description": "Comments, votes and topic requests (same-origin only)."
    },
    {
      "name": "discovery",
      "description": "Documents that describe the site to agents."
    }
  ],
  "paths": {
    "/api/articles": {
      "get": {
        "deprecated": false,
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "Article slug, as listed by this endpoint. Omit to list.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "examples": [
                "beer-index"
              ]
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Language code. Default en.",
            "schema": {
              "type": "string",
              "enum": [
                "en",
                "fr",
                "es",
                "pt",
                "it",
                "de",
                "ru",
                "ja",
                "ko",
                "zh",
                "ar",
                "he",
                "hi",
                "tr",
                "kab"
              ],
              "default": "en"
            }
          }
        ],
        "operationId": "getArticles",
        "tags": [
          "articles"
        ],
        "summary": "List articles, or read one article's Markdown",
        "description": "Without slug: every published article, newest first. With slug: that article with its Markdown body in content. Unknown lang values fall back to en. Future-dated articles and version variants (slug.vN) return 404. Cached 60 s.",
        "responses": {
          "200": {
            "description": "The list (no slug) or the article (slug).",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArticlesResponse"
                }
              }
            }
          },
          "404": {
            "description": "No published article with that slug.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/city-search": {
      "get": {
        "deprecated": false,
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "City name or part of it. Accents and case are ignored. Truncated to 80 characters.",
            "schema": {
              "type": "string",
              "maxLength": 80,
              "examples": [
                "par"
              ]
            }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Limit to one country, ISO 3166-1 alpha-2.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z]{2}$",
              "examples": [
                "FR"
              ]
            }
          }
        ],
        "operationId": "searchCities",
        "tags": [
          "beer-index"
        ],
        "summary": "Search cities by name",
        "description": "Ranked typeahead over a bundled GeoNames extract (cities of 15,000+ people): exact match, then prefix, word prefix and substring, ties broken by population. At most 8 results. Queries shorter than 2 letters return an empty list. No database. Cached 5 min in the browser, 1 day at the CDN.",
        "responses": {
          "200": {
            "description": "Matching cities.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CitySearchResult"
                }
              }
            }
          },
          "400": {
            "description": "country is not a 2-letter code.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/identity": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getAgentIdentity",
        "tags": [
          "discovery"
        ],
        "summary": "Identity types accepted",
        "description": "The identity_endpoint from /.well-known/oauth-authorization-server. Always answers that anonymous is the only identity type. Nothing to exchange. Cached 1 h.",
        "responses": {
          "200": {
            "description": "Identity document.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentIdentity"
                }
              }
            }
          }
        }
      }
    },
    "/api/topic-requests": {
      "post": {
        "deprecated": false,
        "parameters": [],
        "operationId": "createTopicRequest",
        "tags": [
          "reader-input"
        ],
        "summary": "Suggest an article topic",
        "description": "Stores a topic request as pending and shows it on /answers. Limit: 5 per day. Same-origin only: the Origin header must equal the site origin, so calls from servers or other sites get 403. Body is JSON, at most 16 KiB. When Turnstile captcha is enabled, turnstileToken is required. Rate-limited per salted IP hash; raw IPs are never stored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TopicRequestInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body (field validation), captcha token missing, or captcha rejected.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Missing or foreign Origin header. Writes accept same-origin browser requests only.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body larger than 16 KiB.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not application/json.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached or IP hash temporarily banned. Carries Retry-After.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Database failure, or captcha misconfigured on the server. Carries requestId.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Captcha provider unreachable.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Rate limits could not be verified.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/bug-reports": {
      "post": {
        "deprecated": false,
        "parameters": [],
        "operationId": "createBugReport",
        "tags": [
          "reader-input"
        ],
        "summary": "Report a bug on the site",
        "description": "Stores a reader's bug report for review. Reports are not publicly readable. A pagePath that is not a site path is dropped, not rejected. Limit: 5 per day. Same-origin only: the Origin header must equal the site origin, so calls from servers or other sites get 403. Body is JSON, at most 16 KiB. When Turnstile captcha is enabled, turnstileToken is required. Rate-limited per salted IP hash; raw IPs are never stored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BugReportInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored for review.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body (field validation), captcha token missing, or captcha rejected.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Missing or foreign Origin header. Writes accept same-origin browser requests only.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body larger than 16 KiB.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not application/json.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached or IP hash temporarily banned. Carries Retry-After.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Database failure, or captcha misconfigured on the server. Carries requestId.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Captcha provider unreachable.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Rate limits could not be verified.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/comments": {
      "post": {
        "deprecated": false,
        "parameters": [],
        "operationId": "createComment",
        "tags": [
          "reader-input"
        ],
        "summary": "Comment on an article, or reply",
        "description": "Adds a comment under an article. A reply's parentId must be a comment on the same article, and replies nest at most 4 levels. Limit: 100 per day; 100 in 5 minutes bans the IP hash for 48 h. Same-origin only: the Origin header must equal the site origin, so calls from servers or other sites get 403. Body is JSON, at most 16 KiB. When Turnstile captcha is enabled, turnstileToken is required. Rate-limited per salted IP hash; raw IPs are never stored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CommentInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored; the new comment.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommentCreated"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, captcha missing or rejected, parent comment not on this article, or thread too deep.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Missing or foreign Origin header. Writes accept same-origin browser requests only.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "articleSlug is not a published article.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body larger than 16 KiB.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not application/json.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached or IP hash temporarily banned. Carries Retry-After.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Database failure, or captcha misconfigured on the server. Carries requestId.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Captcha provider unreachable.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Rate limits could not be verified.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/votes": {
      "get": {
        "deprecated": false,
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": true,
            "description": "Article slug, as listed by GET /api/articles.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "examples": [
                "beer-index"
              ]
            }
          }
        ],
        "operationId": "getViewerVotes",
        "tags": [
          "reader-input"
        ],
        "summary": "The caller's own votes on one article",
        "description": "The votes the caller has cast on an article and on its comments, so the page can highlight them. The caller is the salted IP hash of the request, the same key POST /api/votes stores a vote under; no token or cookie is read, and no hash is returned. A comment the caller has not voted on is absent from comments. Open like every read: no Origin header needed, no quota spent. Never cached (Cache-Control: private, no-store).",
        "responses": {
          "200": {
            "description": "The caller's votes. All zero and empty for a caller who has not voted.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ViewerVotes"
                }
              }
            }
          },
          "404": {
            "description": "slug is missing, or is not a published article.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "deprecated": false,
        "parameters": [],
        "operationId": "castVote",
        "tags": [
          "reader-input"
        ],
        "summary": "Vote on an article or a comment",
        "description": "Toggles a vote: sending the same direction again removes it, the other direction switches it. One vote per target per IP hash. Limit: 60 vote changes per minute. No captcha. Same-origin only: the Origin header must equal the site origin. Body is JSON, at most 16 KiB.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VoteInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New score and this voter's current vote.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoteResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Missing or foreign Origin header. Writes accept same-origin browser requests only.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown article, or the comment no longer exists.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body larger than 16 KiB.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not application/json.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "More than 60 vote changes in a minute, or the IP hash is temporarily banned. Carries Retry-After.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Database failure. Carries requestId.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/city-submissions": {
      "post": {
        "deprecated": false,
        "parameters": [],
        "operationId": "createCitySubmission",
        "tags": [
          "beer-index",
          "reader-input"
        ],
        "summary": "Submit a city to the Beer Index",
        "description": "Stores a city with local pay, rent and beer prices for review. Country name, coordinates and region are derived on the server from the bundled dataset, never taken from the body. Submissions are not publicly readable. Limit: 3 per day. Same-origin only: the Origin header must equal the site origin, so calls from servers or other sites get 403. Body is JSON, at most 16 KiB. When Turnstile captcha is enabled, turnstileToken is required. Rate-limited per salted IP hash; raw IPs are never stored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CitySubmissionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored for review.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, unknown country, rent not below net pay, or captcha missing or rejected.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Missing or foreign Origin header. Writes accept same-origin browser requests only.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body larger than 16 KiB.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not application/json.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached or IP hash temporarily banned. Carries Retry-After.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Database failure, or captcha misconfigured on the server. Carries requestId.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Captcha provider unreachable.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Rate limits could not be verified.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getLlmsTxt",
        "tags": [
          "discovery"
        ],
        "summary": "llms.txt",
        "description": "What the site is, when to use it, how to read and act, developer links, and the article index. Cached 60 s.",
        "responses": {
          "200": {
            "description": "Markdown served as plain text.",
            "headers": {},
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PlainTextDocument"
                }
              }
            }
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getLlmsFullTxt",
        "tags": [
          "discovery"
        ],
        "summary": "llms.txt plus every article's full text",
        "description": "llms.txt followed by the Markdown of every published English article. Cached 60 s.",
        "responses": {
          "200": {
            "description": "Markdown served as plain text.",
            "headers": {},
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PlainTextDocument"
                }
              }
            }
          }
        }
      }
    },
    "/agents.md": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getAgentsMd",
        "tags": [
          "discovery"
        ],
        "summary": "Guide for AI agents",
        "description": "What the site is, when to use it, how to read, how to act, limits, links. Markdown with YAML frontmatter.",
        "responses": {
          "200": {
            "description": "Markdown.",
            "headers": {},
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              }
            }
          }
        }
      }
    },
    "/auth.md": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getAuthMd",
        "tags": [
          "discovery"
        ],
        "summary": "Authentication walkthrough",
        "description": "Leads with a top-level heading, per the WorkOS auth.md spec, and explains that there is no authentication: open reads, same-origin writes, anonymous identity.",
        "responses": {
          "200": {
            "description": "Markdown, first line a level-1 heading.",
            "headers": {},
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getOpenApiSpec",
        "tags": [
          "discovery"
        ],
        "summary": "This OpenAPI document",
        "description": "The OpenAPI 3.1 description of this API. Cached 1 h.",
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 document.",
            "headers": {},
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiDocument"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/ard.json": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getArdManifest",
        "tags": [
          "discovery"
        ],
        "summary": "Agentic Resource Discovery manifest",
        "description": "ARD entries for the OpenAPI description, the agent skill and llms.txt. The same document is served at /.well-known/ai-catalog.json.",
        "responses": {
          "200": {
            "description": "ARD manifest.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArdManifest"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/ai-catalog.json": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getAiCatalog",
        "tags": [
          "discovery"
        ],
        "summary": "Legacy ARD path",
        "description": "Same document as /.well-known/ard.json, at the predecessor path.",
        "responses": {
          "200": {
            "description": "ARD manifest.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArdManifest"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/agent-skills/index.json": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getAgentSkillsIndex",
        "tags": [
          "discovery"
        ],
        "summary": "Agent skills index",
        "description": "Agent Skills Discovery index (schema 0.2.0) with one skill-md entry and its sha256 digest. Also served at /.well-known/agent-skills.",
        "responses": {
          "200": {
            "description": "Skills index.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentSkillsIndex"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/agent-skills/askgib/SKILL.md": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getAgentSkill",
        "tags": [
          "discovery"
        ],
        "summary": "The askgib agent skill",
        "description": "SKILL.md: frontmatter with name and description, then the instructions, including a \"When to use this\" section naming the jobs this site is for.",
        "responses": {
          "200": {
            "description": "Markdown.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getProtectedResourceMetadata",
        "tags": [
          "discovery"
        ],
        "summary": "OAuth protected resource metadata (RFC 9728)",
        "description": "States that no bearer methods or scopes exist. Points at /auth.md.",
        "responses": {
          "200": {
            "description": "RFC 9728 document.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getAuthorizationServerMetadata",
        "tags": [
          "discovery"
        ],
        "summary": "OAuth authorization server metadata (RFC 8414)",
        "description": "No grant types, no response types; agent_auth lists anonymous as the only identity type.",
        "responses": {
          "200": {
            "description": "RFC 8414 document.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationServerMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/api-catalog": {
      "get": {
        "deprecated": false,
        "parameters": [],
        "operationId": "getApiCatalog",
        "tags": [
          "discovery"
        ],
        "summary": "API catalog (RFC 9727)",
        "description": "A linkset naming this API's description documents: service-desc (this OpenAPI document), service-doc (the developer docs, agents.md and llms.txt) and service-meta (the ARD manifest and the protected-resource metadata). Served as application/linkset+json with the RFC 9727 profile. Cached 1 h.",
        "responses": {
          "200": {
            "description": "RFC 9727 linkset.",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/API-Version"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/linkset+json;profile=\"https://www.rfc-editor.org/info/rfc9727\"": {
                "schema": {
                  "$ref": "#/components/schemas/ApiCatalog"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "API-Version": {
        "description": "The API version that answered (semver). Equal to info.version in /openapi.json. On every /api response.",
        "schema": {
          "type": "string",
          "pattern": "^\\d+\\.\\d+\\.\\d+$",
          "examples": [
            "1.0.0"
          ]
        }
      },
      "Deprecation": {
        "description": "RFC 9745. Sent only by an operation marked deprecated: true in this document: the date it was deprecated, as a structured-field date. No operation sends it today. It is followed by removal no sooner than 90 days later; see Sunset.",
        "schema": {
          "type": "string",
          "pattern": "^@\\d+$",
          "examples": [
            "@1798761600"
          ]
        }
      },
      "Sunset": {
        "description": "RFC 8594. Sent together with Deprecation: the HTTP-date after which the deprecated operation stops answering.",
        "schema": {
          "type": "string",
          "examples": [
            "Thu, 01 Apr 2027 00:00:00 GMT"
          ]
        }
      },
      "RateLimit-Policy": {
        "description": "The quotas this API enforces, as a structured-fields list. A read spends none of them.",
        "schema": {
          "type": "string",
          "examples": [
            "\"comments\";q=100;w=86400, \"topic-requests\";q=5;w=86400, \"city-submissions\";q=3;w=86400, \"bug-reports\";q=5;w=86400, \"votes\";q=60;w=60"
          ]
        }
      },
      "RateLimit": {
        "description": "The quota this request used: remaining (r) and seconds until reset (t).",
        "schema": {
          "type": "string",
          "examples": [
            "\"topic-requests\";r=4;t=86400"
          ]
        }
      },
      "RateLimit-Limit": {
        "description": "Requests allowed in the window.",
        "schema": {
          "type": "integer",
          "examples": [
            5
          ]
        }
      },
      "RateLimit-Remaining": {
        "description": "Requests left in the window after this one.",
        "schema": {
          "type": "integer",
          "examples": [
            4
          ]
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the quota is free again. The windows are sliding, so this is the window length: the worst case.",
        "schema": {
          "type": "integer",
          "examples": [
            86400
          ]
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer",
          "examples": [
            86400
          ]
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "description": "Every error is JSON. Unknown /api paths add code, message, hint, documentation and openapi.",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          },
          "requestId": {
            "type": "string",
            "description": "Present on 5xx backend failures; quote it when reporting."
          },
          "code": {
            "type": "string",
            "examples": [
              "not_found"
            ]
          },
          "message": {
            "type": "string"
          },
          "hint": {
            "type": "string",
            "description": "What to do next."
          },
          "documentation": {
            "type": "string",
            "format": "uri"
          },
          "openapi": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Ok": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "const": true
          }
        }
      },
      "CitySearchResult": {
        "type": "object",
        "required": [
          "results"
        ],
        "description": "Up to 8 cities, best match first.",
        "properties": {
          "results": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "$ref": "#/components/schemas/CityMatch"
            }
          }
        }
      },
      "CommentCreated": {
        "type": "object",
        "required": [
          "ok",
          "comment"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "comment": {
            "$ref": "#/components/schemas/Comment"
          }
        }
      },
      "VoteResult": {
        "type": "object",
        "required": [
          "score",
          "userVote"
        ],
        "properties": {
          "score": {
            "type": "integer",
            "description": "Score of the target after the toggle."
          },
          "userVote": {
            "type": "integer",
            "enum": [
              1,
              -1,
              0
            ],
            "description": "0 when the vote was removed."
          }
        }
      },
      "ViewerVotes": {
        "type": "object",
        "required": [
          "article",
          "comments"
        ],
        "properties": {
          "article": {
            "type": "integer",
            "enum": [
              1,
              -1,
              0
            ],
            "description": "The caller's vote on the article; 0 for none."
          },
          "comments": {
            "type": "object",
            "description": "The caller's vote on each comment of the article they voted on, by comment id.",
            "additionalProperties": {
              "type": "integer",
              "enum": [
                1,
                -1
              ]
            }
          }
        }
      },
      "MarkdownDocument": {
        "type": "string",
        "contentMediaType": "text/markdown",
        "description": "A Markdown document. /auth.md and SKILL.md lead with a level-1 heading."
      },
      "PlainTextDocument": {
        "type": "string",
        "contentMediaType": "text/plain",
        "description": "Markdown served as text/plain, as llms.txt requires."
      },
      "OpenApiDocument": {
        "type": "object",
        "required": [
          "openapi",
          "info",
          "paths"
        ],
        "description": "An OpenAPI 3.1 document; this file is one.",
        "properties": {
          "openapi": {
            "type": "string",
            "const": "3.1.0"
          },
          "info": {
            "type": "object",
            "required": [
              "title",
              "version"
            ],
            "properties": {
              "title": {
                "type": "string"
              },
              "version": {
                "type": "string",
                "examples": [
                  "1.0.0"
                ]
              },
              "summary": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "contact": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          },
          "servers": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "security": {
            "type": "array",
            "maxItems": 0,
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Empty: reads need no credential."
          },
          "x-api-policy": {
            "$ref": "#/components/schemas/ApiPolicy"
          },
          "paths": {
            "type": "object",
            "additionalProperties": true,
            "description": "Path item objects, keyed by path."
          },
          "components": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "ApiPolicy": {
        "type": "object",
        "required": [
          "version",
          "versioning",
          "stability",
          "versionHeader",
          "pathVersioning",
          "deprecation"
        ],
        "description": "The version and deprecation policy of this API, as data. The prose is in info.description.",
        "properties": {
          "version": {
            "type": "string",
            "pattern": "^\\d+\\.\\d+\\.\\d+$",
            "description": "The live version; equal to info.version."
          },
          "versioning": {
            "type": "string",
            "const": "semver"
          },
          "stability": {
            "type": "string",
            "enum": [
              "stable",
              "beta",
              "deprecated"
            ]
          },
          "versionHeader": {
            "type": "string",
            "description": "Response header that names the version on every /api response."
          },
          "pathVersioning": {
            "type": "string"
          },
          "compatibility": {
            "type": "string"
          },
          "deprecation": {
            "type": "object",
            "required": [
              "operationFlag",
              "headers",
              "minimumNoticeDays"
            ],
            "properties": {
              "operationFlag": {
                "type": "string",
                "const": "deprecated"
              },
              "headers": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "Deprecation",
                    "Sunset"
                  ]
                }
              },
              "specifications": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "minimumNoticeDays": {
                "type": "integer",
                "minimum": 1
              }
            }
          },
          "deprecatedOperations": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "operationIds on their way out. Empty today."
          },
          "changelog": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ArdEntry": {
        "type": "object",
        "required": [
          "identifier",
          "displayName",
          "type"
        ],
        "description": "One agentic resource. Carries exactly one of url or data.",
        "properties": {
          "identifier": {
            "type": "string",
            "pattern": "^urn:air:",
            "examples": [
              "urn:air:www.askgib.com:api:askgib-public-api"
            ]
          },
          "displayName": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "IANA media type of the resource this entry points at."
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Inline alternative to url."
          },
          "description": {
            "type": "string"
          },
          "representativeQueries": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "capabilities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "updatedAt": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "ArdManifest": {
        "type": "object",
        "required": [
          "specVersion",
          "host",
          "entries"
        ],
        "description": "Agentic Resource Discovery / AI Catalog manifest.",
        "properties": {
          "specVersion": {
            "type": "string",
            "examples": [
              "1.0"
            ]
          },
          "host": {
            "type": "object",
            "required": [
              "displayName",
              "identifier"
            ],
            "properties": {
              "displayName": {
                "type": "string"
              },
              "identifier": {
                "type": "string",
                "examples": [
                  "did:web:www.askgib.com"
                ]
              },
              "url": {
                "type": "string",
                "format": "uri"
              },
              "description": {
                "type": "string"
              }
            }
          },
          "updatedAt": {
            "type": "string",
            "format": "date"
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArdEntry"
            }
          }
        }
      },
      "AgentSkillEntry": {
        "type": "object",
        "required": [
          "name",
          "type",
          "description",
          "url",
          "digest"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "const": "skill-md"
          },
          "description": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Path to SKILL.md, relative to the site origin."
          },
          "digest": {
            "type": "string",
            "pattern": "^sha256:[a-f0-9]{64}$"
          },
          "whenToUse": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The jobs this skill is for, each with the call that does it."
          }
        }
      },
      "AgentSkillsIndex": {
        "type": "object",
        "required": [
          "$schema",
          "skills"
        ],
        "properties": {
          "$schema": {
            "type": "string",
            "format": "uri"
          },
          "skills": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentSkillEntry"
            }
          }
        }
      },
      "ProtectedResourceMetadata": {
        "type": "object",
        "required": [
          "resource",
          "authorization_servers"
        ],
        "description": "RFC 9728. Empty arrays are the point: nothing to present.",
        "properties": {
          "resource": {
            "type": "string",
            "format": "uri"
          },
          "resource_name": {
            "type": "string"
          },
          "authorization_servers": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "bearer_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 0
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 0
          },
          "resource_documentation": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "AuthorizationServerMetadata": {
        "type": "object",
        "required": [
          "issuer",
          "grant_types_supported",
          "agent_auth"
        ],
        "description": "RFC 8414 plus the WorkOS auth.md agent_auth block.",
        "properties": {
          "issuer": {
            "type": "string",
            "format": "uri"
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 0
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 0
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string",
              "const": "none"
            }
          },
          "service_documentation": {
            "type": "string",
            "format": "uri"
          },
          "agent_auth": {
            "type": "object",
            "required": [
              "skill",
              "identity_endpoint",
              "identity_types_supported"
            ],
            "properties": {
              "skill": {
                "type": "string",
                "format": "uri"
              },
              "identity_endpoint": {
                "type": "string",
                "format": "uri"
              },
              "identity_types_supported": {
                "type": "array",
                "items": {
                  "const": "anonymous"
                }
              }
            }
          }
        }
      },
      "LinksetLink": {
        "type": "object",
        "required": [
          "href"
        ],
        "properties": {
          "href": {
            "type": "string",
            "format": "uri"
          },
          "type": {
            "type": "string",
            "description": "Media type of the target."
          },
          "title": {
            "type": "string"
          }
        }
      },
      "ApiCatalog": {
        "type": "object",
        "required": [
          "linkset"
        ],
        "description": "RFC 9727 API catalog: a linkset of link context objects.",
        "properties": {
          "linkset": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "anchor"
              ],
              "properties": {
                "anchor": {
                  "type": "string",
                  "format": "uri"
                },
                "service-desc": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LinksetLink"
                  }
                },
                "service-doc": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LinksetLink"
                  }
                },
                "service-meta": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LinksetLink"
                  }
                },
                "author": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LinksetLink"
                  }
                }
              }
            }
          }
        }
      },
      "ArticleSummary": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "date",
          "excerpt",
          "readingTime",
          "lang",
          "url"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "excerpt": {
            "type": "string"
          },
          "readingTime": {
            "type": "integer",
            "minimum": 1,
            "description": "Minutes."
          },
          "lang": {
            "type": "string",
            "enum": [
              "en",
              "fr",
              "es",
              "pt",
              "it",
              "de",
              "ru",
              "ja",
              "ko",
              "zh",
              "ar",
              "he",
              "hi",
              "tr",
              "kab"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The article's page in this language: /articles/<slug> in English, /<lang>/articles/<slug> in another language (for example /fr/articles/<slug>). The same URL with .md is its Markdown."
          }
        }
      },
      "ArticlesResponse": {
        "type": "object",
        "description": "What GET /api/articles answers: an ArticleList when slug is omitted, an Article when slug is given.",
        "required": [
          "lang"
        ],
        "oneOf": [
          {
            "$ref": "#/components/schemas/ArticleList"
          },
          {
            "$ref": "#/components/schemas/Article"
          }
        ]
      },
      "ArticleList": {
        "type": "object",
        "required": [
          "site",
          "lang",
          "count",
          "articles"
        ],
        "properties": {
          "site": {
            "type": "string",
            "format": "uri"
          },
          "lang": {
            "type": "string",
            "enum": [
              "en",
              "fr",
              "es",
              "pt",
              "it",
              "de",
              "ru",
              "ja",
              "ko",
              "zh",
              "ar",
              "he",
              "hi",
              "tr",
              "kab"
            ]
          },
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "articles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArticleSummary"
            }
          }
        }
      },
      "Article": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "excerpt",
          "date",
          "editedDate",
          "lang",
          "readingTime",
          "url",
          "content"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "excerpt": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "editedDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "lang": {
            "type": "string",
            "enum": [
              "en",
              "fr",
              "es",
              "pt",
              "it",
              "de",
              "ru",
              "ja",
              "ko",
              "zh",
              "ar",
              "he",
              "hi",
              "tr",
              "kab"
            ],
            "description": "The language of the returned text: the requested language, or en when the article has no translation in it. `url` is in the same language."
          },
          "readingTime": {
            "type": "integer",
            "minimum": 1
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The article's page in this language: /articles/<slug> in English, /<lang>/articles/<slug> in another language (for example /fr/articles/<slug>). The same URL with .md is its Markdown."
          },
          "content": {
            "type": "string",
            "description": "Markdown (MDX). Component tags can be skipped."
          }
        }
      },
      "CityMatch": {
        "type": "object",
        "required": [
          "city",
          "country",
          "countryIso2",
          "currency",
          "region",
          "admin",
          "lat",
          "lng",
          "population"
        ],
        "properties": {
          "city": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "countryIso2": {
            "type": "string",
            "pattern": "^[A-Z]{2}$"
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217; empty for a few territories."
          },
          "region": {
            "type": "string",
            "description": "Beer Index region bucket."
          },
          "admin": {
            "type": "string",
            "description": "State or province; empty when none."
          },
          "lat": {
            "type": "number"
          },
          "lng": {
            "type": "number"
          },
          "population": {
            "type": "integer"
          }
        }
      },
      "AgentIdentity": {
        "type": "object",
        "required": [
          "resource",
          "identity_types_supported",
          "anonymous"
        ],
        "properties": {
          "resource": {
            "type": "string",
            "format": "uri"
          },
          "identity_types_supported": {
            "type": "array",
            "items": {
              "const": "anonymous"
            }
          },
          "anonymous": {
            "type": "object",
            "properties": {
              "reads": {
                "type": "string"
              },
              "writes": {
                "type": "string"
              },
              "documentation": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "TopicRequestInput": {
        "type": "object",
        "required": [
          "requestTitle"
        ],
        "properties": {
          "requestTitle": {
            "type": "string",
            "minLength": 1,
            "maxLength": 240,
            "description": "Trimmed."
          },
          "context": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1500
          },
          "requesterName": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "turnstileToken": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token. Required only when captcha is enabled on the server."
          },
          "browserIdentityToken": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^bg_v1_[a-f0-9]{64}$",
            "description": "Optional display-name token from the site's own script. Malformed values are ignored, not rejected."
          }
        }
      },
      "BugReportInput": {
        "type": "object",
        "required": [
          "description"
        ],
        "properties": {
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "Trimmed."
          },
          "pagePath": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Site path the report is about; must start with \"/\". Anything else is stored as null."
          },
          "reporterName": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "turnstileToken": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token. Required only when captcha is enabled on the server."
          },
          "browserIdentityToken": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^bg_v1_[a-f0-9]{64}$",
            "description": "Optional display-name token from the site's own script. Malformed values are ignored, not rejected."
          }
        }
      },
      "CommentInput": {
        "type": "object",
        "required": [
          "articleSlug",
          "content"
        ],
        "properties": {
          "articleSlug": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          },
          "content": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "Trimmed."
          },
          "parentId": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[A-Za-z0-9-]{1,64}$",
            "description": "Comment being replied to."
          },
          "posterName": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "turnstileToken": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token. Required only when captcha is enabled on the server."
          },
          "browserIdentityToken": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^bg_v1_[a-f0-9]{64}$",
            "description": "Optional display-name token from the site's own script. Malformed values are ignored, not rejected."
          }
        }
      },
      "Comment": {
        "type": "object",
        "required": [
          "id",
          "article_slug",
          "parent_id",
          "content",
          "display_name",
          "display_color",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "article_slug": {
            "type": "string"
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "content": {
            "type": "string"
          },
          "display_name": {
            "type": "string",
            "description": "Anonymous colour-and-animal name, prefixed by posterName when given."
          },
          "display_color": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VoteInput": {
        "type": "object",
        "required": [
          "type",
          "targetId",
          "vote"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "article",
              "comment"
            ]
          },
          "targetId": {
            "type": "string",
            "description": "Article slug (^[a-z0-9]+(?:-[a-z0-9]+)*$) or comment id (^[A-Za-z0-9-]{1,64}$)."
          },
          "vote": {
            "type": "integer",
            "enum": [
              1,
              -1
            ]
          },
          "browserIdentityToken": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^bg_v1_[a-f0-9]{64}$",
            "description": "Optional display-name token from the site's own script. Malformed values are ignored, not rejected."
          }
        }
      },
      "CitySubmissionInput": {
        "type": "object",
        "required": [
          "city",
          "countryIso2",
          "currency",
          "netMonthly",
          "rent",
          "barBeer",
          "storeBeer"
        ],
        "properties": {
          "city": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "countryIso2": {
            "type": "string",
            "pattern": "^[A-Za-z]{2}$"
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Za-z]{2,5}$",
            "description": "Upper-cased on the server."
          },
          "netMonthly": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1000000000000,
            "description": "Net monthly pay, local currency."
          },
          "rent": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1000000000000,
            "description": "Monthly rent; must be below netMonthly."
          },
          "barBeer": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1000000000,
            "description": "Price of a beer in a bar."
          },
          "storeBeer": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1000000000,
            "description": "Price of a beer in a shop."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 240
          },
          "turnstileToken": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token. Required only when captcha is enabled on the server."
          },
          "browserIdentityToken": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^bg_v1_[a-f0-9]{64}$",
            "description": "Optional display-name token from the site's own script. Malformed values are ignored, not rejected."
          }
        }
      }
    }
  }
}