rules: # ─── INFO / METADATA ─────────────────────────────────────────────────────── info-title-required: message: API info title is required severity: error given: $.info then: field: title function: truthy info-title-contains-unified: message: API title should reference Unified.to severity: warn given: $.info.title then: function: pattern functionOptions: match: "(?i)unified" info-description-required: message: API info description is required severity: warn given: $.info then: field: description function: truthy info-version-required: message: API info version is required severity: error given: $.info then: field: version function: truthy # ─── OPENAPI VERSION ──────────────────────────────────────────────────────── openapi-version-3: message: OpenAPI version must be 3.x severity: error given: $ then: field: openapi function: pattern functionOptions: match: "^3\\." # ─── SERVERS ──────────────────────────────────────────────────────────────── servers-defined: message: At least one server must be defined severity: error given: $ then: field: servers function: truthy servers-https-only: message: Server URLs must use HTTPS severity: error given: $.servers[*].url then: function: pattern functionOptions: match: "^https://" servers-unified-to-domain: message: Server should be on api.unified.to or regional subdomain severity: info given: $.servers[*].url then: function: pattern functionOptions: match: "unified\\.to" # ─── PATHS — NAMING CONVENTIONS ───────────────────────────────────────────── paths-connection-id-required: message: API resource paths should include {connection_id} parameter severity: warn given: $.paths[*]~ then: function: pattern functionOptions: match: "^/(unified|[a-z]+/\\{connection_id\\})" paths-kebab-case: message: Path segments should use snake_case or kebab-case (Unified.to convention) severity: warn given: $.paths[*]~ then: function: pattern functionOptions: match: "^(/[a-z0-9_{}/-]+)+$" paths-no-trailing-slash: message: Paths must not end with a trailing slash severity: warn given: $.paths[*]~ then: function: pattern functionOptions: notMatch: "/$" # ─── OPERATIONS ───────────────────────────────────────────────────────────── operation-id-required: message: Every operation must have an operationId severity: error given: $.paths[*][get,post,put,patch,delete,head,options] then: field: operationId function: truthy operation-summary-required: message: Every operation must have a summary severity: error given: $.paths[*][get,post,put,patch,delete,head,options] then: field: summary function: truthy operation-summary-title-case: message: Operation summaries should use Title Case severity: warn given: $.paths[*][get,post,put,patch,delete,head,options].summary then: function: pattern functionOptions: match: "^[A-Z]" operation-tags-required: message: Every operation should have at least one tag severity: warn given: $.paths[*][get,post,put,patch,delete,head,options] then: field: tags function: truthy operation-description-required: message: Every operation should have a description severity: info given: $.paths[*][get,post,put,patch,delete,head,options] then: field: description function: truthy # ─── PARAMETERS ───────────────────────────────────────────────────────────── parameter-connection-id-path: message: connection_id path parameter should be defined when present severity: error given: $.paths[*][get,post,put,patch,delete].parameters[?(@.name=='connection_id')] then: field: in function: enumeration functionOptions: values: - path parameter-description-required: message: All parameters should have descriptions severity: warn given: $.paths[*][get,post,put,patch,delete].parameters[*] then: field: description function: truthy parameter-pagination-limit: message: List operations should support a limit parameter severity: info given: $.paths[*].get.parameters[?(@.name=='limit')] then: field: schema.type function: enumeration functionOptions: values: - integer - number parameter-snake-case: message: Parameter names should use snake_case (Unified.to convention) severity: warn given: $.paths[*][get,post,put,patch,delete].parameters[*].name then: function: pattern functionOptions: match: "^[a-z][a-z0-9_]*$" # ─── REQUEST BODIES ────────────────────────────────────────────────────────── request-body-json: message: Request bodies should use application/json content type severity: warn given: $.paths[*][post,put,patch].requestBody.content then: function: schema functionOptions: schema: type: object minProperties: 1 # ─── RESPONSES ─────────────────────────────────────────────────────────────── response-success-defined: message: Every operation must define at least one 2xx success response severity: error given: $.paths[*][get,post,put,patch,delete] then: field: responses function: truthy response-description-required: message: All responses must have a description severity: error given: $.paths[*][get,post,put,patch,delete].responses[*] then: field: description function: truthy response-401-unauthorized: message: Protected operations should define 401 Unauthorized response severity: warn given: $.paths[*][get,post,put,patch,delete] then: field: responses.401 function: truthy response-422-validation: message: Write operations should define a 422 validation error response severity: info given: $.paths[*][post,put,patch] then: field: responses.422 function: truthy # ─── SCHEMAS — PROPERTY NAMING ─────────────────────────────────────────────── schema-property-snake-case: message: Schema properties should use snake_case (Unified.to convention) severity: warn given: $.components.schemas[*].properties[*]~ then: function: pattern functionOptions: match: "^[a-z][a-z0-9_]*$" schema-raw-property-present: message: Domain model schemas should include a 'raw' property for provider-specific data severity: info given: $.components.schemas[?(@.type=='object')] then: field: properties.raw function: truthy # ─── SECURITY ──────────────────────────────────────────────────────────────── security-schemes-defined: message: Security schemes must be defined severity: error given: $.components then: field: securitySchemes function: truthy security-jwt-bearer: message: Unified.to uses JWT bearer token authentication severity: warn given: $.components.securitySchemes[*][?(@.type=='http')] then: field: scheme function: enumeration functionOptions: values: - bearer # ─── HTTP METHOD CONVENTIONS ───────────────────────────────────────────────── get-no-request-body: message: GET operations must not have a request body severity: error given: $.paths[*].get then: field: requestBody function: falsy delete-returns-200-or-204: message: DELETE operations should return 200 (with body) or 204 (no content) severity: info given: $.paths[*].delete.responses then: function: schema functionOptions: schema: type: object minProperties: 1 # ─── GENERAL QUALITY ───────────────────────────────────────────────────────── no-empty-descriptions: message: Descriptions must not be empty strings severity: warn given: $..description then: function: pattern functionOptions: match: ".+" components-schemas-defined: message: Components schemas section should be defined for reusable types severity: error given: $.components then: field: schemas function: truthy schema-raw-passthrough: message: >- Each domain model should have a 'raw' passthrough object for provider-specific fields not covered by the unified schema. severity: info given: $.components.schemas[?(!@.title)] then: function: truthy