AppstoreSpy · API Governance Rules

AppstoreSpy API Rules

Spectral linting rules defining API design standards and conventions for AppstoreSpy.

12 Rules error 3 warn 5 info 4
View Rules File View on GitHub

Rule Categories

appstorespy

Rules

error
appstorespy-operation-security
Every operation declares the security it requires. AppstoreSpy meters and bills per call, so an operation with no declared scheme is either a contract error or an unbilled surface.
$.paths[*][get,put,post,delete,patch]
error
appstorespy-no-credential-in-query
The API key travels in the API-KEY request header, never in the query string, where it would land in server, proxy and referrer logs.
$.components.securitySchemes[?(@.type == 'apiKey')]
error
appstorespy-operation-error-response
Every operation documents at least one failure response. Callers integrate against the error path as much as the success path.
$.paths[*][get,put,post,delete,patch].responses
warn
appstorespy-operation-description
Every operation carries a description. The summary names the operation; the description is what a consumer, or an agent choosing between 37 operations, actually reads.
$.paths[*][get,put,post,delete,patch]
warn
appstorespy-store-tag
Every operation is tagged with the surface it belongs to, so the reference groups into navigable sections instead of one flat list.
$.paths[*][get,put,post,delete,patch]
warn
appstorespy-path-namespace
Paths live under one of the three published namespaces: /play for Google Play, /ios for the App Store, /jobs for asynchronous crawl jobs.
$.paths
warn
appstorespy-fields-param-documented
The `fields` parameter selects which columns come back and takes a comma-separated list. Its description has to say so, because the shape is not inferable from the type.
$.paths[*][*].parameters[?(@.name == 'fields')]
warn
appstorespy-query-param-snake-case
Query parameter names are snake_case across the whole surface.
$.paths[*][*].parameters[?(@.in == 'query')]
info
appstorespy-sort-param-example
Sorting uses a `-field` prefix for descending order. A `sort` parameter carries an example, because the convention is not discoverable from the type alone.
$.paths[*][*].parameters[?(@.name == 'sort')]
info
appstorespy-summary-length
Summaries stay short enough to render in a reference index.
$.paths[*][get,put,post,delete,patch].summary
info
appstorespy-info-version-released
info.version identifies a released contract rather than a framework default. The callable surface is pinned at /v1 in servers[0].url.
$.info
info
appstorespy-problem-details
Failure responses should carry application/problem+json (RFC 9457) rather than a bare vendor envelope. Recorded as guidance: the surface is on vendor JSON today and moving is a breaking change for existing callers.
$.paths[*][*].responses[?(@property.match(/^4/))].content

Spectral Ruleset

Raw ↑
# AppstoreSpy API ruleset
#
# What "good" means for the AppstoreSpy contract, written down so it is a
# reviewable decision rather than reviewer taste. Extends spectral:oas for the
# baseline OpenAPI rules and adds the conventions specific to this surface:
# API-KEY header auth on every operation, the /play, /ios and /jobs namespaces,
# the `fields` and `sort` query conventions, and snake_case parameters.
#
# Run against the published contract:
#   spectral lint https://api.appstorespy.com/openapi.json \
#     --ruleset rules/appstorespy-spectral-rules.yml

extends:
  - spectral:oas

rules:
  # ---------------------------------------------------------------- errors ---

  appstorespy-operation-security:
    description: >-
      Every operation declares the security it requires. AppstoreSpy meters and
      bills per call, so an operation with no declared scheme is either a
      contract error or an unbilled surface.
    message: '{{path}} declares no security requirement'
    severity: error
    given: $.paths[*][get,put,post,delete,patch]
    then:
      field: security
      function: truthy

  appstorespy-no-credential-in-query:
    description: >-
      The API key travels in the API-KEY request header, never in the query
      string, where it would land in server, proxy and referrer logs.
    message: 'securityScheme {{property}} places the credential in the query string'
    severity: error
    given: $.components.securitySchemes[?(@.type == 'apiKey')]
    then:
      field: in
      function: pattern
      functionOptions:
        notMatch: '^query$'

  appstorespy-operation-error-response:
    description: >-
      Every operation documents at least one failure response. Callers integrate
      against the error path as much as the success path.
    message: '{{path}} documents no 4xx response'
    severity: error
    given: $.paths[*][get,put,post,delete,patch].responses
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          anyOf:
            - required: ['400']
            - required: ['401']
            - required: ['403']
            - required: ['404']
            - required: ['422']
            - required: ['429']

  # ----------------------------------------------------------------- warns ---

  appstorespy-operation-description:
    description: >-
      Every operation carries a description. The summary names the operation;
      the description is what a consumer, or an agent choosing between 37
      operations, actually reads.
    message: '{{path}} has no description'
    severity: warn
    given: $.paths[*][get,put,post,delete,patch]
    then:
      field: description
      function: truthy

  appstorespy-store-tag:
    description: >-
      Every operation is tagged with the surface it belongs to, so the reference
      groups into navigable sections instead of one flat list.
    message: '{{path}} carries no recognised surface tag'
    severity: warn
    given: $.paths[*][get,put,post,delete,patch]
    then:
      field: tags
      function: schema
      functionOptions:
        schema:
          type: array
          contains:
            enum:
              - Google Play
              - App Store
              - Jobs
              - Events
              - Suggestions
              - Search Filter v.2

  appstorespy-path-namespace:
    description: >-
      Paths live under one of the three published namespaces: /play for Google
      Play, /ios for the App Store, /jobs for asynchronous crawl jobs.
    message: '{{property}} is outside the /play, /ios and /jobs namespaces'
    severity: warn
    given: $.paths
    then:
      field: '@key'
      function: pattern
      functionOptions:
        match: '^/(play|ios|jobs)/'

  appstorespy-fields-param-documented:
    description: >-
      The `fields` parameter selects which columns come back and takes a
      comma-separated list. Its description has to say so, because the shape is
      not inferable from the type.
    message: 'the `fields` parameter does not document the comma-separated convention'
    severity: warn
    given: $.paths[*][*].parameters[?(@.name == 'fields')]
    then:
      field: description
      function: pattern
      functionOptions:
        match: 'comma'

  appstorespy-query-param-snake-case:
    description: Query parameter names are snake_case across the whole surface.
    message: 'query parameter {{value}} is not snake_case'
    severity: warn
    given: $.paths[*][*].parameters[?(@.in == 'query')]
    then:
      field: name
      function: casing
      functionOptions:
        type: snake

  # ----------------------------------------------------------------- infos ---

  appstorespy-sort-param-example:
    description: >-
      Sorting uses a `-field` prefix for descending order. A `sort` parameter
      carries an example, because the convention is not discoverable from the
      type alone.
    message: 'the `sort` parameter carries no example of the -field convention'
    severity: info
    given: $.paths[*][*].parameters[?(@.name == 'sort')]
    then:
      field: example
      function: truthy

  appstorespy-summary-length:
    description: Summaries stay short enough to render in a reference index.
    message: 'summary is longer than 60 characters'
    severity: info
    given: $.paths[*][get,put,post,delete,patch].summary
    then:
      function: length
      functionOptions:
        max: 60

  appstorespy-info-version-released:
    description: >-
      info.version identifies a released contract rather than a framework
      default. The callable surface is pinned at /v1 in servers[0].url.
    message: 'info.version is the framework default and identifies no release'
    severity: info
    given: $.info
    then:
      field: version
      function: pattern
      functionOptions:
        notMatch: '^0\.0\.1$'

  appstorespy-problem-details:
    description: >-
      Failure responses should carry application/problem+json (RFC 9457) rather
      than a bare vendor envelope. Recorded as guidance: the surface is on
      vendor JSON today and moving is a breaking change for existing callers.
    message: '{{path}} does not offer application/problem+json'
    severity: info
    given: $.paths[*][*].responses[?(@property.match(/^4/))].content
    then:
      field: application/problem+json
      function: truthy

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/appstorespy-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.