API Evangelist · API Governance Rules
API Evangelist API Rules
Spectral linting rules defining API design standards and conventions for API Evangelist.
47 Rules
error 8
warn 31
info 8
Rule Categories
apikey
components
error
info
no
openapi
operation
pagination
parameter
path
query
response
schema
security
servers
tag
Rules
error
openapi-version-supported
The document must declare OpenAPI 3.1 or 3.2. Both contracts are 3.1.0.
$
warn
info-contact-complete
info.contact must carry a name, an email and a URL. A contract that cannot be asked a question about itself is a dead end for whoever inherits the integration.
$.info
warn
info-license-present
info.license must say what the consumer may do with the contract and the data behind it. Measured: the v1 contract declares CC BY-SA 4.0; governance declares none.
$.info
info
info-summary-present
info.summary gives an agent one line to route on without parsing the whole description. Measured: present on v1, absent on governance.
$.info
warn
info-description-substantive
info.description must be a real orientation, not a restated title -- at least 200 characters covering what the API is for and how access works.
$.info.description
warn
info-version-semver
info.version must be semantic (MAJOR.MINOR.PATCH). Measured 1.2.0 and 1.0.0.
$.info
error
servers-defined
The document must declare at least one server, or the contract is not callable.
$
error
servers-https-only
Server URLs must use HTTPS. Measured 100% already compliant.
$.servers[*].url
warn
servers-expected-domain
Server URLs must be on apievangelist.com. A contract published under this provider that points somewhere else is either mis-filed or a copied template.
$.servers[*].url
warn
servers-no-trailing-slash
Server URLs must not end in a slash -- concatenating one with a path that also starts with a slash is the single most common cause of a 404 in a generated client.
$.servers[*].url
warn
path-segments-snake-case
Path segments and path parameters must be snake_case. Measured snake @ 97% (n=144); the 4 exceptions are all in the governance contract -- agent-descriptor, agent-export, context-gate, pipeline-audit.
$.paths
warn
path-no-trailing-slash
Paths must not end in a slash (the root path excepted). A contract that documents both /x and /x/ documents two resources it does not have.
$.paths
warn
path-no-file-extension
Paths must not carry a file extension. Format is a matter for Accept or an explicit `format` parameter, not for the resource identifier.
$.paths
warn
path-params-snake-case
Path parameter names must be snake_case. Measured snake @ 100% (n=25).
$.paths[*].parameters[?(@.in=='path')].name
error
operation-operationid-present
Every operation must carry an operationId. It is the name every generated client method and every MCP tool derives from; without it the generator invents one.
$.paths[*][get,put,post,delete,patch,head,options]
warn
operation-operationid-camel-case
operationIds must be camelCase. Measured camel @ 100% (n=92) -- the rule holds the line rather than reporting a backlog.
$.paths[*][get,put,post,delete,patch,head,options].operationId
warn
operation-summary-present
One line saying what the operation does. Measured 92/92 already compliant.
$.paths[*][get,put,post,delete,patch,head,options]
info
operation-summary-concise
A summary is a label, not a paragraph -- keep it under 120 characters and put the rest in the description, which is where a reference renderer expects it.
$.paths[*][get,put,post,delete,patch,head,options].summary
warn
operation-description-present
Every operation must describe itself beyond its summary -- what it returns, what it costs, and which tier it needs.
$.paths[*][get,put,post,delete,patch,head,options]
warn
operation-tags-present
Every operation must be tagged. Tags are the navigation of the reference and the split key for every per-resource artifact derived from this contract.
$.paths[*][get,put,post,delete,patch,head,options]
warn
operation-single-tag
Exactly one tag per operation. Measured: 0 of 92 operations carry more than one, and a second tag silently duplicates the operation across split artifacts.
$.paths[*][get,put,post,delete,patch,head,options].tags
warn
operation-deprecated-needs-sunset
A deprecated operation must declare `x-sunset` with the date it goes away. Deprecation without a date is a warning nobody can plan against, and the published deprecation policy commits to six months' notice.
$.paths[*][get,put,post,delete,patch,head,options]
warn
query-params-snake-case
Query parameter names must be snake_case. Measured snake @ 100% (n=175).
$.paths[*][get,put,post,delete,patch,head,options].parameters[?(@.in=='query')].name
warn
components-parameters-pascal-case
Reusable parameter component KEYS must be PascalCase, so a $ref reads as a type name while the wire-level `name` stays snake_case. Measured pascal @ 100% (n=9).
$.components.parameters
info
parameter-description-present
Every parameter must say what it does. Measured: 6 distinct parameters carry no description, including `limit` and `page`.
$.components.parameters[*]$.paths[*][get,put,post,delete,patch,head,options].parameters[?(@.name)]
warn
parameter-schema-typed
Every parameter must declare a schema. An untyped parameter is a string by accident, and every generated client will treat it as one.
$.components.parameters[*]$.paths[*][get,put,post,delete,patch,head,options].parameters[?(@.name)]
warn
pagination-limit-bounded
A `limit` parameter must declare a maximum, or the contract promises a page size the service will refuse. Measured: components.parameters.Limit is correctly bounded 1..100 with a default of 25.
$.components.parameters[?(@.name == 'limit')]$.paths[*][get,put,post,delete,patch,head,options].parameters[?(@.name == 'limit')]
warn
response-2xx-present
Every operation must document a success response. Measured: 91 of 92 do -- /auth/login documents only a 302, which is a real gap for any non-browser client.
$.paths[*][get,put,post,delete,patch,head,options]
warn
response-error-documented
Every operation must document at least one failure. Measured: only 48 of 92 do, so two thirds of this API's error behaviour is undocumented.
$.paths[*][get,put,post,delete,patch,head,options]
warn
response-description-present
Every response must describe the condition it represents.
$.paths[*][get,put,post,delete,patch,head,options].responses[*]
info
response-json-media-type
Responses that carry a body must offer application/json. Measured: 25 of 26 media types are application/json; the exception is the text/html sign-in redirect.
$.paths[*][get,put,post,delete,patch,head,options].responses[*].content
info
response-example-present
Response bodies should carry an example or examples object. Agents and generated SDKs read examples as the shape contract; a schema alone leaves every consumer guessing at real values. Measured 63/66 on v1, 0/26 on governance.
$.paths[*][get,put,post,delete,patch,head,options].responses[*].content[*]
info
response-headers-documented
Successful responses should document their headers. Rate-limit and pagination signals are part of the contract, and an agent that cannot see them cannot pace itself. Measured: 0 of 92 operations document any response header.
$.paths[*][get,put,post,delete,patch,head,options].responses[?(@property.match(/^2/))]
warn
error-response-shares-a-schema
A 4xx or 5xx body must $ref a shared error schema rather than redefining the shape inline, so one error contract covers the whole API.
$.paths[*][get,put,post,delete,patch,head,options].responses[?(@property.match(/^[45]/))].content[*]
warn
error-schema-defined
A shared `Error` schema must exist in components. Anchored at the document root for the same reason as security-schemes-defined: the governance contract defines no components.schemas whatsoever, so a rule given $.components.schemas selected nothing and reported it clean. Measured: defined in v1, absent from governance -- which is why that contract has no error shape to $ref.
$
warn
schema-names-pascal-case
Component schema names must be PascalCase. Measured pascal @ 100% (n=7).
$.components.schemas
warn
schema-properties-snake-case
Schema properties must be snake_case. Measured snake @ 100% (n=20).
$.components.schemas[*].properties
warn
schema-has-type
Every component schema must declare a type. Measured: 5 of 7 do. An untyped schema validates nothing and generates nothing.
$.components.schemas[*]
info
schema-has-description
Every component schema must describe the thing it models. Measured: 1 of 7 do.
$.components.schemas[*]
info
schema-property-descriptions
Every schema property should describe what it holds -- property names carry less meaning to a consumer than the author assumes. Measured: 17 of 20 have none.
$.components.schemas[*].properties[*]
warn
tag-has-description
Declared tags must have descriptions -- they are the navigation of the reference and of every tag-split artifact derived from it. Measured 21/21 compliant.
$.tags[*]
error
security-schemes-defined
Security schemes must be defined in components. Anchored at the document root, not at $.components -- a document with no components object at all selects nothing under $.components, and the rule would go silent on exactly the case it exists to catch. Measured: both contracts declare one.
$
warn
security-applied-at-root
A root-level `security` array must state the default posture for every operation, including the keyless tier, rather than leaving it to be inferred.
$
error
apikey-scheme-in-header
An apiKey scheme must be carried in a header or a cookie -- never a query string, where the credential lands in access logs, referrers and browser history.
$.components.securitySchemes[*]
warn
apikey-header-name-consistent
Header-borne API keys must use `x-api-key` across the estate. Measured: both contracts already do, and a client configured for one should work against the other.
$.components.securitySchemes[?(@.type == 'apiKey' && @.in == 'header')]
error
no-credentials-in-query
No parameter may carry a credential in the query string. Matches the common names -- api_key, apikey, token, access_token, password, secret.
$.components.parameters[?(@.in == 'query')].name$.paths[*][get,put,post,delete,patch,head,options].parameters[?(@.in == 'query')].name
error
no-credentials-in-path
No path parameter may carry a credential. A secret in a path is a secret in every proxy log and every shared URL.
$.paths[*].parameters[?(@.in == 'path')].name
Spectral Ruleset
Work with this as data
Every ruleset here is available over the APIs.io API and to AI agents over MCP.