# Spectral ruleset for SpotOn APIs # Enforces the conventions observed across the SpotOn Restaurant POS Export API # and the SpotOn Reserve API: kebab-case paths, camelCase parameters and schema # properties, x-api-key header authentication, Title Case tags, and complete # operation metadata. extends: [[spectral:oas, off]] rules: # ── INFO / METADATA ───────────────────────────────────────────── info-title-spoton-prefix: description: API title should start with "SpotOn". severity: warn given: $.info then: field: title function: pattern functionOptions: match: "^SpotOn" info-description-required: description: Info object must have a description of at least 40 characters. severity: warn given: $.info then: field: description function: length functionOptions: min: 40 info-version-required: description: Info object must declare a version. severity: error given: $.info then: field: version function: truthy info-contact-required: description: Info object should include contact information. severity: info given: $.info then: field: contact function: truthy # ── OPENAPI VERSION ───────────────────────────────────────────── openapi-version-3: description: Specs must be OpenAPI 3.0.x. severity: warn given: $ then: field: openapi function: pattern functionOptions: match: "^3\\.0\\." # ── SERVERS ───────────────────────────────────────────────────── servers-defined: description: A servers array must be defined. severity: error given: $ then: field: servers function: truthy servers-https-only: description: Server URLs must use HTTPS. severity: error given: $.servers[*] then: field: url function: pattern functionOptions: match: "^https://" servers-have-description: description: Each server should have a description (for example Production or Sandbox). severity: warn given: $.servers[*] then: field: description function: truthy # ── PATHS — NAMING CONVENTIONS ───────────────────────────────── paths-kebab-case: description: Path segments must be lowercase kebab-case (path parameters in camelCase braces allowed). severity: warn given: $.paths[*]~ then: function: pattern functionOptions: match: "^(/([a-z0-9-]+|\\{[a-zA-Z0-9]+\\}))+$" paths-no-trailing-slash: description: Paths must not end with a trailing slash. severity: error given: $.paths[*]~ then: function: pattern functionOptions: notMatch: ".+/$" # ── OPERATIONS ───────────────────────────────────────────────── operation-operationid-required: description: Every operation must declare an operationId. severity: error given: $.paths[*][get,post,put,patch,delete] then: field: operationId function: truthy operation-operationid-camelcase: description: operationId should be camelCase. severity: warn given: $.paths[*][get,post,put,patch,delete] then: field: operationId function: pattern functionOptions: match: "^[a-z][a-zA-Z0-9]*$" 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-spoton-prefix: description: Operation summaries should start with "SpotOn". severity: warn given: $.paths[*][get,post,put,patch,delete] then: field: summary function: pattern functionOptions: match: "^SpotOn " 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-tags-required: description: Every operation must be tagged. severity: warn given: $.paths[*][get,post,put,patch,delete] then: field: tags function: truthy # ── TAGS ──────────────────────────────────────────────────────── tag-has-description: description: Global tags should have descriptions. severity: info given: $.tags[*] then: field: description function: truthy tag-title-case: description: Tag names should be Title Case. severity: warn given: $.tags[*] then: field: name function: pattern functionOptions: match: "^[A-Z][A-Za-z0-9]*( [A-Z][A-Za-z0-9]*)*$" # ── PARAMETERS ───────────────────────────────────────────────── parameter-description-required: description: Parameters must have a description. severity: warn given: $.paths[*][get,post,put,patch,delete].parameters[*] then: field: description function: truthy parameter-camelcase: description: Parameter names should be camelCase. severity: warn given: $.paths[*][get,post,put,patch,delete].parameters[?(@.name && @.in != 'header')] then: field: name function: pattern functionOptions: match: "^[a-z][a-zA-Z0-9]*$" parameter-no-apikey-in-query: description: API keys must be passed in the x-api-key header, never in query parameters. severity: error given: $.paths[*][get,post,put,patch,delete].parameters[?(@.in == 'query')] then: field: name function: pattern functionOptions: notMatch: "(?i)(api[-_]?key|apikey|x-api-key)" # ── REQUEST BODIES ───────────────────────────────────────────── request-body-json: description: Request bodies should offer an application/json representation. severity: warn given: $.paths[*][post,put,patch].requestBody.content then: field: application/json function: truthy # ── RESPONSES ────────────────────────────────────────────────── response-success-defined: description: Operations must define a 200 success response. severity: error given: $.paths[*][get,post,put,patch,delete].responses then: field: "200" function: truthy response-unauthorized-defined: description: Operations should define a 401 Unauthorized response. severity: warn given: $.paths[*][get,post,put,patch,delete].responses then: field: "401" function: truthy response-forbidden-defined: description: Operations should define a 403 Forbidden response for per-location and per-restaurant access. severity: warn given: $.paths[*][get,post,put,patch,delete].responses then: field: "403" function: truthy response-server-error-defined: description: Operations should define a 500 server error response. severity: info given: $.paths[*][get,post,put,patch,delete].responses then: field: "500" function: truthy error-schema-has-message: description: The Error schema must include a message property. severity: warn given: $.components.schemas.Error.properties then: field: message function: truthy # ── SCHEMAS — PROPERTY NAMING ────────────────────────────────── schema-property-camelcase: description: Schema property names should be camelCase. severity: warn given: $.components.schemas[*].properties[*]~ then: function: pattern functionOptions: match: "^[a-z][a-zA-Z0-9]*$" schema-property-has-type-or-ref: description: Schema properties must declare a type or a $ref. severity: warn given: $.components.schemas[*].properties[*] then: function: schema functionOptions: schema: anyOf: - required: [type] - required: ["$ref"] schema-top-level-description: description: Top-level component schemas should have a description. severity: info given: $.components.schemas[*] then: field: description function: truthy # ── SECURITY ─────────────────────────────────────────────────── security-global-defined: description: A global security requirement must be declared. severity: warn given: $ then: field: security function: truthy security-apikey-scheme-defined: description: An apiKeyAuth security scheme using the x-api-key header must be defined. severity: error given: $.components.securitySchemes.apiKeyAuth then: function: schema functionOptions: schema: type: object required: [type, in, name] properties: type: const: apiKey in: const: header name: const: x-api-key # ── HTTP METHOD CONVENTIONS ──────────────────────────────────── get-no-request-body: description: GET operations must not declare a request body. severity: error given: $.paths[*].get then: field: requestBody function: falsy post-has-request-body: description: POST operations should declare a request body. severity: warn given: $.paths[*].post then: field: requestBody function: truthy # ── GENERAL QUALITY ──────────────────────────────────────────── microcks-operation-present: description: Operations should carry an x-microcks-operation extension for mock-server compatibility. severity: info given: $.paths[*][get,post,put,patch,delete] then: field: x-microcks-operation function: truthy