# 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
# jokeapi — 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)
# - query-params-casing: snake @ 81% (n=21)
# - operationid-casing: camel @ 90% (n=10)
# - schema-names-casing: pascal @ 100% (n=15)
# - schema-properties-casing: snake @ 86% (n=78)
# - error-schema-defined: JokeError
# - merge: kept 42 existing, added 4 measured, upgraded 0
# - added: query-params-casing, schema-names-casing, schema-properties-casing, error-schema-defined
extends:
- spectral:oas
rules:
info-title-jokeapi:
description: info.title MUST be "JokeAPI" (the canonical product name).
message: '{{property}}: title should equal "JokeAPI"'
severity: error
given: $.info
then:
field: title
function: pattern
functionOptions:
match: ^JokeAPI$
info-description-required:
description: info.description MUST be present with at least 80 characters.
message: '{{property}}: description is missing or too short (<80 chars)'
severity: error
given: $.info
then:
field: description
function: length
functionOptions:
min: 80
info-version-required:
description: info.version MUST be present and follow semver.
message: '{{property}}: version is missing or not semver'
severity: error
given: $.info
then:
field: version
function: pattern
functionOptions:
match: ^\d+\.\d+(\.\d+)?$
info-license-mit:
description: License MUST be declared as MIT (JokeAPI is MIT-licensed).
message: '{{property}}: license.name should be "MIT"'
severity: warn
given: $.info.license
then:
field: name
function: pattern
functionOptions:
match: ^MIT$
info-contact-required:
description: Contact information SHOULD be present.
message: '{{property}}: contact block is missing'
severity: info
given: $.info
then:
field: contact
function: truthy
openapi-version-3-0:
description: Use OpenAPI 3.0.x — Microcks and tooling compatibility.
message: '{{property}}: openapi version should match 3.0.x'
severity: warn
given: $.openapi
then:
function: pattern
functionOptions:
match: ^3\.0\.
servers-required:
description: servers MUST be defined and non-empty.
message: '{{property}}: at least one server entry is required'
severity: error
given: $
then:
field: servers
function: truthy
server-url-https:
description: Every server URL MUST use HTTPS.
message: '{{value}} must use https://'
severity: error
given: $.servers[*].url
then:
function: pattern
functionOptions:
match: ^https://
server-canonical-host:
description: At least one server URL SHOULD point at v2.jokeapi.dev.
message: '{{property}}: no canonical v2.jokeapi.dev server entry found'
severity: info
given: $
then:
field: servers
function: schema
functionOptions:
schema:
type: array
contains:
type: object
properties:
url:
type: string
pattern: ^https://v2\.jokeapi\.dev
required:
- url
path-kebab-case:
description: Path segments MUST be lowercase kebab-case.
message: '{{property}}: path segments must be lowercase kebab-case'
severity: warn
given: $.paths.*~
then:
function: pattern
functionOptions:
match: ^(/[a-z0-9-]+|/\{[a-zA-Z]+\})+$
path-no-trailing-slash:
description: Paths MUST NOT end with a trailing slash.
message: '{{property}}: path must not end with /'
severity: error
given: $.paths.*~
then:
function: pattern
functionOptions:
notMatch: .+/$
path-no-query-string:
description: Paths MUST NOT contain a query string.
message: '{{property}}: path must not contain ?'
severity: error
given: $.paths.*~
then:
function: pattern
functionOptions:
notMatch: \?
operation-id-required:
description: Every operation MUST have an operationId.
message: '{{property}}: operationId is missing'
severity: error
given: $.paths.*[get,post,put,patch,delete]
then:
field: operationId
function: truthy
operation-id-camel-case:
description: operationId MUST be camelCase.
message: '{{value}}: 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-summary-required:
description: Every operation MUST have a summary.
message: '{{property}}: summary is missing'
severity: error
given: $.paths.*[get,post,put,patch,delete]
then:
field: summary
function: truthy
operation-summary-jokeapi-prefix:
description: Operation summary MUST start with "JokeAPI ".
message: '{{value}}: summary must start with "JokeAPI "'
severity: warn
given: $.paths.*[get,post,put,patch,delete].summary
then:
function: pattern
functionOptions:
match: '^JokeAPI '
operation-description-required:
description: Every operation MUST have a description of at least 30 characters.
message: '{{property}}: description is missing or too short'
severity: warn
given: $.paths.*[get,post,put,patch,delete]
then:
field: description
function: length
functionOptions:
min: 30
operation-tags-required:
description: Every operation MUST be tagged.
message: '{{property}}: tags array is missing or empty'
severity: error
given: $.paths.*[get,post,put,patch,delete]
then:
field: tags
function: truthy
operation-microcks-extension:
description: Every operation SHOULD declare an x-microcks-operation extension.
message: '{{property}}: x-microcks-operation is missing'
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 defined.
message: '{{property}}: global tags array is missing'
severity: warn
given: $
then:
field: tags
function: truthy
tag-title-case:
description: Tag names MUST be Title Case (e.g. "Jokes", "Metadata", "System").
message: '{{value}}: tag name must be Title Case'
severity: warn
given: $.tags[*].name
then:
function: pattern
functionOptions:
match: ^[A-Z][a-zA-Z]*( [A-Z][a-zA-Z]*)*$
tag-description-required:
description: Every global tag MUST have a description.
message: '{{property}}: tag description is missing'
severity: warn
given: $.tags[*]
then:
field: description
function: truthy
parameter-description-required:
description: Every parameter MUST have a description.
message: '{{property}}: parameter description is missing'
severity: warn
given: $.paths.*.*.parameters[*]
then:
field: description
function: truthy
parameter-schema-required:
description: Every parameter MUST have a schema with type.
message: '{{property}}: parameter schema/type is missing'
severity: error
given: $.paths.*.*.parameters[*]
then:
field: schema
function: truthy
parameter-camel-or-kebab-case:
description: Parameter names follow JokeAPI's actual convention — camelCase (`blacklistFlags`, `idRange`)
or kebab-case (`safe-mode`).
message: '{{value}}: parameter name must be camelCase or kebab-case'
severity: warn
given: $.paths.*.*.parameters[*].name
then:
function: pattern
functionOptions:
match: ^[a-z][a-zA-Z0-9-]*$
parameter-format-enum:
description: '`format` parameter SHOULD declare its enum [json, xml, yaml, txt].'
message: '{{property}}: format parameter should enumerate json/xml/yaml/txt'
severity: info
given: $.paths.*.*.parameters[?(@.name=='format')]
then:
field: schema.enum
function: truthy
request-body-json-content:
description: Request bodies MUST advertise application/json content.
message: '{{property}}: request body must include application/json'
severity: warn
given: $.paths.*.*.requestBody.content
then:
field: application/json
function: truthy
request-body-description:
description: Request bodies SHOULD have a description.
message: '{{property}}: requestBody description is missing'
severity: info
given: $.paths.*.*.requestBody
then:
field: description
function: truthy
response-2xx-required:
description: Every operation MUST document at least one 2xx response.
message: '{{property}}: no 2xx response defined'
severity: error
given: $.paths.*[get,post,put,patch,delete].responses
then:
function: schema
functionOptions:
schema:
type: object
patternProperties:
^2\d\d$:
type: object
minProperties: 1
response-429-documented:
description: Operations SHOULD document a 429 response (JokeAPI rate-limits at 120 req/min).
message: '{{property}}: 429 response is missing — JokeAPI is rate-limited'
severity: info
given: $.paths.*[get,post,put,patch,delete].responses
then:
field: '429'
function: truthy
response-description-required:
description: Every response MUST have a description.
message: '{{property}}: response description is missing'
severity: error
given: $.paths.*.*.responses.*
then:
field: description
function: truthy
response-json-content:
description: Responses SHOULD include application/json content.
message: '{{property}}: response missing application/json content'
severity: info
given: $.paths.*.*.responses.*.content
then:
field: application/json
function: truthy
schema-description-required:
description: Top-level component schemas MUST have a description.
message: '{{property}}: schema description is missing'
severity: warn
given: $.components.schemas.*
then:
field: description
function: truthy
schema-type-required:
description: Top-level component schemas MUST declare a type.
message: '{{property}}: schema type is missing'
severity: error
given: $.components.schemas.*
then:
field: type
function: truthy
schema-property-camel-case:
description: Schema property names use the same casing as the live API (camelCase or single-word lowercase).
message: '{{value}}: schema property must be camelCase or single-word lowercase'
severity: info
given: $.components.schemas.*.properties.*~
then:
function: pattern
functionOptions:
match: ^[a-z][a-zA-Z0-9]*$
schema-error-envelope:
description: JokeAPI error responses follow the `{error, code, message, causedBy, timestamp}` envelope.
message: '{{property}}: JokeError schema must include all canonical envelope fields'
severity: warn
given: $.components.schemas.JokeError
then:
field: required
function: schema
functionOptions:
schema:
type: array
contains:
type: string
enum:
- error
- code
- message
- causedBy
- timestamp
schema-timestamp-integer:
description: '`timestamp` properties MUST be `integer` (Unix epoch milliseconds).'
message: '{{property}}: timestamp must be type integer'
severity: warn
given: $..properties.timestamp
then:
field: type
function: pattern
functionOptions:
match: ^integer$
no-security-schemes:
description: JokeAPI requires NO authentication — security schemes MUST NOT be declared.
message: '{{property}}: JokeAPI is open; do not declare securitySchemes'
severity: warn
given: $.components
then:
field: securitySchemes
function: falsy
get-no-request-body:
description: GET operations MUST NOT have a request body.
message: '{{property}}: GET operation must not declare a requestBody'
severity: error
given: $.paths.*.get
then:
field: requestBody
function: falsy
post-has-request-body:
description: POST operations SHOULD declare a request body.
message: '{{property}}: POST operation should declare a requestBody'
severity: warn
given: $.paths.*.post
then:
field: requestBody
function: truthy
no-empty-descriptions:
description: Descriptions MUST NOT be empty whitespace.
message: '{{property}}: description is empty'
severity: warn
given: $..description
then:
function: pattern
functionOptions:
notMatch: ^\s*$
examples-on-schemas:
description: Schema properties SHOULD carry examples to power Microcks mocks.
message: '{{property}}: property has no example value'
severity: info
given: $.components.schemas.*.properties.*
then:
field: example
function: truthy
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: info
given: $.components.schemas[*].properties
then:
field: '@key'
function: casing
functionOptions:
type: snake
error-schema-defined:
description: A shared error schema (JokeError) should be defined for error payloads.
severity: warn
given: $.components.schemas
then:
field: JokeError
function: truthy
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.