Stack Exchange · API Governance Rules

Stack Exchange API Rules

Spectral linting rules defining API design standards and conventions for Stack Exchange.

48 Rules error 16 warn 24 info 8
Generated by API Evangelist Written by API Evangelist tooling for Stack Exchange's APIs. It is a starting point, not a ruleset Stack Exchange publishes or enforces.
View Rules File View on GitHub

Rule Categories

examples http info no openapi operation parameter paths query response schema security servers tag tags

Rules

error
info-title-stack-exchange
info.title must start with "Stack Exchange".
$.info.title
error
info-description-required
info.description is required and must be at least 60 characters.
$.info
error
info-version-required
info.version must follow Stack Exchange API versioning (2.x).
$.info.version
warn
info-terms-of-service-required
Stack Exchange API specs must reference the API Terms of Use.
$.info
warn
info-contact-url-required
info.contact.url must point to api.stackexchange.com.
$.info.contact
error
openapi-version-3
Only OpenAPI 3.0.x is supported for Stack Exchange specs.
$.openapi
error
servers-defined
servers array must be present with at least one entry.
$
error
servers-must-be-stackexchange-https
Server URL must be https://api.stackexchange.com/{version}.
$.servers[*].url
warn
servers-have-descriptions
Each server entry should have a description.
$.servers[*]
warn
paths-lowercase-kebab
Path segments must be lowercase with kebab-case for multi-word resources.
$.paths
error
paths-no-trailing-slash
Paths must not end with a trailing slash.
$.paths
error
paths-no-query-strings
Paths must not contain query strings; use parameters instead.
$.paths
warn
paths-camelcase-path-params
Path parameter placeholders should use camelCase (e.g. {ids}, {accessTokens}).
$.paths
info
paths-known-collections-plural
Stack Exchange root collections (questions, answers, comments, users, tags, badges, sites, posts, revisions, suggested-edits, events, filters, notifications) are plural nouns.
$.paths
error
operation-summary-required
Every operation must have a summary.
$.paths[*][get,post,put,patch,delete]
warn
operation-summary-stack-exchange-prefix
Operation summaries must begin with the company name "Stack Exchange".
$.paths[*][get,post,put,patch,delete].summary
warn
operation-description-required
Every operation must have a description.
$.paths[*][get,post,put,patch,delete]
error
operation-id-required
Every operation must have an operationId.
$.paths[*][get,post,put,patch,delete]
warn
operation-id-camelcase
operationId must be camelCase.
$.paths[*][get,post,put,patch,delete].operationId
warn
operation-id-verb-prefix
operationId should start with a known REST verb (list, get, search, find, create, update, delete, invalidate, read, deauthenticate).
$.paths[*][get,post,put,patch,delete].operationId
error
operation-tags-required
Every operation must be tagged with at least one tag.
$.paths[*][get,post,put,patch,delete]
info
operation-microcks-extension
Each operation should declare an x-microcks-operation block.
$.paths[*][get,post,put,patch,delete]
warn
tags-global-defined
Global tags array must be present with entries.
$
warn
tag-name-title-case
Tag names use Title Case (e.g. "Suggested Edits", "Access Tokens").
$.tags[*].name
info
tag-description-required
Each tag should have a description.
$.tags[*]
warn
parameter-description-required
Every reusable parameter must have a description.
$.components.parameters[*]
warn
parameter-snake-case-names
Query parameter names use snake_case (or single lowercase word).
$.paths[*][get,post,put,patch,delete].parameters[?(@.in=='query')].name
warn
parameter-pagination-canonical
Pagination uses `page` (1-indexed) and `pagesize` (max 100).
$.paths[*][get,post,put,patch,delete].parameters[?(@.name=='page' || @.name=='pagesize')]
warn
parameter-site-required-on-per-site-methods
Per-site methods must declare the `site` query parameter as required.
$.paths[*][get].parameters[?(@.name=='site' || @.$ref=='#/components/parameters/Site')]
info
parameter-api-key-in-query
API key is exchanged via the `key` query parameter (per Stack Exchange convention).
$.components.securitySchemes.apiKey
error
response-success-required
Every operation must declare a 2xx response.
$.paths[*][get,post,put,patch,delete].responses
warn
response-json-content
Success responses must include application/json content.
$.paths[*][get,post,put,patch,delete].responses['200','201','202','204']
info
response-schema-references-wrapper
Every success response schema should be (or extend) the Wrapper envelope.
$.paths[*][get,post,put,patch,delete].responses['200'].content.application/json.schema.$ref
error
response-description-required
Every response must have a description.
$.paths[*][get,post,put,patch,delete].responses[*]
warn
schema-property-snake-case
Schema properties use snake_case (Stack Exchange wire convention).
$.components.schemas[*].properties.*~
error
schema-wrapper-fields-present
The Wrapper schema must define has_more, quota_max, and quota_remaining.
$.components.schemas.Wrapper.properties
info
schema-timestamps-int64
Stack Exchange timestamps are Unix epoch seconds stored as int64.
$.components.schemas[*].properties[creation_date,last_activity_date,last_modified_date,last_access_date,last_edit_date,closed_date,protected_date,locked_date,community_owned_date,launch_date,expires_on_date,on_date]
warn
schema-types-defined
Every component schema must declare a type (object/array/string/...).
$.components.schemas[*]
error
security-schemes-defined
securitySchemes must declare oauth2 (write/private) and apiKey (quota).
$.components.securitySchemes
error
security-oauth2-stackexchange-endpoints
OAuth2 endpoints must point to stackoverflow.com.
$.components.securitySchemes.oauth2.flows.authorizationCode
warn
security-oauth2-known-scopes
Only Stack Exchange OAuth scopes are allowed (read_inbox, no_expiry, write_access, private_info).
$.components.securitySchemes.oauth2.flows.authorizationCode.scopes
info
http-methods-read-mostly
Stack Exchange v2.3 is read-mostly — GET is the dominant verb.
$.paths[*]
warn
no-empty-descriptions
Descriptions must not be empty strings.
$..description
info
examples-encouraged
Schema properties should have example values for documentation and mocking.
$.components.schemas[*].properties[?(@.type=='string' || @.type=='integer' || @.type=='boolean')]
warn
servers-expected-domain
Server URLs should be on the stackexchange.com domain.
$.servers[*].url
warn
query-params-casing
Query parameters should be snake_case (the dominant convention in this API).
$.paths[*][get,post,put,patch,delete].parameters[?(@.in=='query')]
warn
schema-names-casing
Component schema names should be PascalCase (the dominant convention in this API).
$.components.schemas
warn
schema-properties-casing
Schema properties should be snake_case (the dominant convention in this API).
$.components.schemas[*].properties

Spectral Ruleset

Raw ↑
# authorship: generated by API Evangelist tooling. Stamped 2026-08-18
# on the file's own generator header (roadmap#64). An unmarked file is
# NOT assumed to be ours -- absence of evidence was never stamped.
x-method: generated
# stackexchange — Spectral ruleset (strengthened)
# Plain Spectral. Existing hand-authored rules preserved; measured rules added
# from this provider's own OpenAPI conventions by strengthen_ruleset.py,
# then self-validated against the spec.
#
# Provenance:
#   - servers-https-only: 100% of servers already https (error)
#   - servers-expected-domain: 1/1 servers on stackexchange.com
#   - path-params-casing: snake @ 92% (n=38)
#   - query-params-casing: snake @ 100% (n=328)
#   - operationid-casing: camel @ 100% (n=70)
#   - schema-names-casing: pascal @ 100% (n=43)
#   - schema-properties-casing: snake @ 100% (n=228)
#   - security: global (root) — NOT emitting operation-security-required
#   - pagination params observed: ['page']
#   - merge: kept 44 existing, added 4 measured, upgraded 0
#   - added: servers-expected-domain, query-params-casing, schema-names-casing, schema-properties-casing
extends:
  - spectral:oas
rules:
  info-title-stack-exchange:
    description: info.title must start with "Stack Exchange".
    message: 'info.title should start with ''Stack Exchange'' (was: {{value}}).'
    severity: error
    given: $.info.title
    then:
      function: pattern
      functionOptions:
        match: ^Stack Exchange
  info-description-required:
    description: info.description is required and must be at least 60 characters.
    message: info.description must be present and ≥ 60 characters.
    severity: error
    given: $.info
    then:
    - field: description
      function: truthy
    - field: description
      function: length
      functionOptions:
        min: 60
  info-version-required:
    description: info.version must follow Stack Exchange API versioning (2.x).
    message: 'info.version must be of the form ''2.x'' (was: {{value}}).'
    severity: error
    given: $.info.version
    then:
      function: pattern
      functionOptions:
        match: ^2\.[0-9]+$
  info-terms-of-service-required:
    description: Stack Exchange API specs must reference the API Terms of Use.
    severity: warn
    given: $.info
    then:
      field: termsOfService
      function: truthy
  info-contact-url-required:
    description: info.contact.url must point to api.stackexchange.com.
    severity: warn
    given: $.info.contact
    then:
      field: url
      function: pattern
      functionOptions:
        match: stackexchange\.com
  openapi-version-3:
    description: Only OpenAPI 3.0.x is supported for Stack Exchange specs.
    severity: error
    given: $.openapi
    then:
      function: pattern
      functionOptions:
        match: ^3\.0\.
  servers-defined:
    description: servers array must be present with at least one entry.
    severity: error
    given: $
    then:
      field: servers
      function: schema
      functionOptions:
        schema:
          type: array
          minItems: 1
  servers-must-be-stackexchange-https:
    description: Server URL must be https://api.stackexchange.com/{version}.
    severity: error
    given: $.servers[*].url
    then:
      function: pattern
      functionOptions:
        match: ^https://api\.stackexchange\.com/2\.[0-9]+$
  servers-have-descriptions:
    description: Each server entry should have a description.
    severity: warn
    given: $.servers[*]
    then:
      field: description
      function: truthy
  paths-lowercase-kebab:
    description: Path segments must be lowercase with kebab-case for multi-word resources.
    message: Path '{{path}}' contains uppercase or non-kebab-case segments.
    severity: warn
    given: $.paths
    then:
      function: pattern
      functionOptions:
        match: ^/[a-z0-9{}/-]*$
      field: '@key'
  paths-no-trailing-slash:
    description: Paths must not end with a trailing slash.
    severity: error
    given: $.paths
    then:
      function: pattern
      functionOptions:
        notMatch: .+/$
      field: '@key'
  paths-no-query-strings:
    description: Paths must not contain query strings; use parameters instead.
    severity: error
    given: $.paths
    then:
      function: pattern
      functionOptions:
        notMatch: \?
      field: '@key'
  paths-camelcase-path-params:
    description: Path parameter placeholders should use camelCase (e.g. {ids}, {accessTokens}).
    severity: warn
    given: $.paths
    then:
      function: pattern
      functionOptions:
        match: ^(/[a-z0-9-]+(/\{[a-z][a-zA-Z0-9]*\})?)*$
      field: '@key'
  paths-known-collections-plural:
    description: Stack Exchange root collections (questions, answers, comments, users, tags, badges, sites,
      posts, revisions, suggested-edits, events, filters, notifications) are plural nouns.
    severity: info
    given: $.paths
    then:
      function: pattern
      functionOptions:
        match: ^/(questions|answers|comments|users|tags|badges|sites|posts|revisions|suggested-edits|events|filters|notifications|inbox|search|search/(advanced|excerpts)|similar|info|me|me/.*|access-tokens/.*|apps/.*|/.*)
      field: '@key'
  operation-summary-required:
    description: Every operation must have a summary.
    severity: error
    given: $.paths[*][get,post,put,patch,delete]
    then:
      field: summary
      function: truthy
  operation-summary-stack-exchange-prefix:
    description: Operation summaries must begin with the company name "Stack Exchange".
    message: 'Operation summary should begin with ''Stack Exchange'' (was: {{value}}).'
    severity: warn
    given: $.paths[*][get,post,put,patch,delete].summary
    then:
      function: pattern
      functionOptions:
        match: '^Stack Exchange '
  operation-description-required:
    description: Every operation must have a description.
    severity: warn
    given: $.paths[*][get,post,put,patch,delete]
    then:
      field: description
      function: truthy
  operation-id-required:
    description: Every operation must have an operationId.
    severity: error
    given: $.paths[*][get,post,put,patch,delete]
    then:
      field: operationId
      function: truthy
  operation-id-camelcase:
    description: operationId must be camelCase.
    severity: warn
    given: $.paths[*][get,post,put,patch,delete].operationId
    then:
      function: pattern
      functionOptions:
        match: ^[a-z][a-zA-Z0-9]*$
  operation-id-verb-prefix:
    description: operationId should start with a known REST verb (list, get, search, find, create, update,
      delete, invalidate, read, deauthenticate).
    severity: warn
    given: $.paths[*][get,post,put,patch,delete].operationId
    then:
      function: pattern
      functionOptions:
        match: ^(list|get|search|find|create|update|delete|invalidate|read|deauthenticate)[A-Z]
  operation-tags-required:
    description: Every operation must be tagged with at least one tag.
    severity: error
    given: $.paths[*][get,post,put,patch,delete]
    then:
      field: tags
      function: schema
      functionOptions:
        schema:
          type: array
          minItems: 1
  operation-microcks-extension:
    description: Each operation should declare an x-microcks-operation block.
    severity: info
    given: $.paths[*][get,post,put,patch,delete]
    then:
      field: x-microcks-operation
      function: truthy
  tags-global-defined:
    description: Global tags array must be present with entries.
    severity: warn
    given: $
    then:
      field: tags
      function: schema
      functionOptions:
        schema:
          type: array
          minItems: 1
  tag-name-title-case:
    description: Tag names use Title Case (e.g. "Suggested Edits", "Access Tokens").
    severity: warn
    given: $.tags[*].name
    then:
      function: pattern
      functionOptions:
        match: ^[A-Z][A-Za-z0-9]*( [A-Z][A-Za-z0-9]*)*$
  tag-description-required:
    description: Each tag should have a description.
    severity: info
    given: $.tags[*]
    then:
      field: description
      function: truthy
  parameter-description-required:
    description: Every reusable parameter must have a description.
    severity: warn
    given: $.components.parameters[*]
    then:
      field: description
      function: truthy
  parameter-snake-case-names:
    description: Query parameter names use snake_case (or single lowercase word).
    severity: warn
    given: $.paths[*][get,post,put,patch,delete].parameters[?(@.in=='query')].name
    then:
      function: pattern
      functionOptions:
        match: ^[a-z][a-z0-9_]*$
  parameter-pagination-canonical:
    description: Pagination uses `page` (1-indexed) and `pagesize` (max 100).
    severity: warn
    given: $.paths[*][get,post,put,patch,delete].parameters[?(@.name=='page' || @.name=='pagesize')]
    then:
      field: in
      function: enumeration
      functionOptions:
        values:
        - query
  parameter-site-required-on-per-site-methods:
    description: Per-site methods must declare the `site` query parameter as required.
    severity: warn
    given: $.paths[*][get].parameters[?(@.name=='site' || @.$ref=='#/components/parameters/Site')]
    then:
      function: truthy
  parameter-api-key-in-query:
    description: API key is exchanged via the `key` query parameter (per Stack Exchange convention).
    severity: info
    given: $.components.securitySchemes.apiKey
    then:
    - field: in
      function: enumeration
      functionOptions:
        values:
        - query
    - field: name
      function: enumeration
      functionOptions:
        values:
        - key
  response-success-required:
    description: Every operation must declare a 2xx response.
    severity: error
    given: $.paths[*][get,post,put,patch,delete].responses
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          patternProperties:
            ^2[0-9]{2}$: {}
          minProperties: 1
  response-json-content:
    description: Success responses must include application/json content.
    severity: warn
    given: $.paths[*][get,post,put,patch,delete].responses['200','201','202','204']
    then:
      field: content.application/json
      function: truthy
  response-schema-references-wrapper:
    description: Every success response schema should be (or extend) the Wrapper envelope.
    severity: info
    given: $.paths[*][get,post,put,patch,delete].responses['200'].content.application/json.schema.$ref
    then:
      function: pattern
      functionOptions:
        match: '#/components/schemas/[A-Z][A-Za-z]+Response$'
  response-description-required:
    description: Every response must have a description.
    severity: error
    given: $.paths[*][get,post,put,patch,delete].responses[*]
    then:
      field: description
      function: truthy
  schema-property-snake-case:
    description: Schema properties use snake_case (Stack Exchange wire convention).
    severity: warn
    given: $.components.schemas[*].properties.*~
    then:
      function: pattern
      functionOptions:
        match: ^[a-z][a-z0-9_]*$
  schema-wrapper-fields-present:
    description: The Wrapper schema must define has_more, quota_max, and quota_remaining.
    severity: error
    given: $.components.schemas.Wrapper.properties
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          required:
          - has_more
          - quota_max
          - quota_remaining
  schema-timestamps-int64:
    description: Stack Exchange timestamps are Unix epoch seconds stored as int64.
    severity: info
    given: $.components.schemas[*].properties[creation_date,last_activity_date,last_modified_date,last_access_date,last_edit_date,closed_date,protected_date,locked_date,community_owned_date,launch_date,expires_on_date,on_date]
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          properties:
            type:
              const: integer
  schema-types-defined:
    description: Every component schema must declare a type (object/array/string/...).
    severity: warn
    given: $.components.schemas[*]
    then:
      field: type
      function: truthy
  security-schemes-defined:
    description: securitySchemes must declare oauth2 (write/private) and apiKey (quota).
    severity: error
    given: $.components.securitySchemes
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          required:
          - oauth2
          - apiKey
  security-oauth2-stackexchange-endpoints:
    description: OAuth2 endpoints must point to stackoverflow.com.
    severity: error
    given: $.components.securitySchemes.oauth2.flows.authorizationCode
    then:
    - field: authorizationUrl
      function: pattern
      functionOptions:
        match: ^https://stackoverflow\.com/oauth$
    - field: tokenUrl
      function: pattern
      functionOptions:
        match: ^https://stackoverflow\.com/oauth/access_token$
  security-oauth2-known-scopes:
    description: Only Stack Exchange OAuth scopes are allowed (read_inbox, no_expiry, write_access, private_info).
    severity: warn
    given: $.components.securitySchemes.oauth2.flows.authorizationCode.scopes
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          additionalProperties: false
          properties:
            read_inbox:
              type: string
            no_expiry:
              type: string
            write_access:
              type: string
            private_info:
              type: string
  http-methods-read-mostly:
    description: Stack Exchange v2.3 is read-mostly — GET is the dominant verb.
    severity: info
    given: $.paths[*]
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          properties:
            get: {}
          minProperties: 1
  no-empty-descriptions:
    description: Descriptions must not be empty strings.
    severity: warn
    given: $..description
    then:
      function: truthy
  examples-encouraged:
    description: Schema properties should have example values for documentation and mocking.
    severity: info
    given: $.components.schemas[*].properties[?(@.type=='string' || @.type=='integer' || @.type=='boolean')]
    then:
      field: example
      function: truthy
  servers-expected-domain:
    description: Server URLs should be on the stackexchange.com domain.
    severity: warn
    given: $.servers[*].url
    then:
      function: pattern
      functionOptions:
        match: stackexchange\.com
  query-params-casing:
    description: Query parameters should be snake_case (the dominant convention in this API).
    severity: warn
    given: $.paths[*][get,post,put,patch,delete].parameters[?(@.in=='query')]
    then:
      field: name
      function: casing
      functionOptions:
        type: snake
  schema-names-casing:
    description: Component schema names should be PascalCase (the dominant convention in this API).
    severity: warn
    given: $.components.schemas
    then:
      field: '@key'
      function: casing
      functionOptions:
        type: pascal
  schema-properties-casing:
    description: Schema properties should be snake_case (the dominant convention in this API).
    severity: warn
    given: $.components.schemas[*].properties
    then:
      field: '@key'
      function: casing
      functionOptions:
        type: snake

Work with this as data

Every ruleset here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for spectral rules

4 MCP tools reach this
  • find_rulesBrowse and filter every ruleset in the catalog.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This ruleset
curl "https://apis.io/api/v1/rules/stackexchange-spectral-rules"
All spectral rules
curl "https://apis.io/api/v1/rules?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.