extends: spectral:oas rules: # API Key Security ternary-security-defined: description: All Ternary API operations must require authentication message: "Operation '{{path}}' must require API key authentication" given: "$.paths[*][get,post,put,patch,delete]" then: field: security function: truthy severity: off ternary-global-security: description: Global security must be defined for Ternary API message: "Ternary API must define global security with API key" given: "$" then: field: security function: truthy severity: error # Versioning ternary-path-versioned: description: All Ternary API paths must start with /v1/ message: "Path '{{property}}' must start with /v1/" given: "$.paths[*]~" then: function: pattern functionOptions: match: "^/v1/" severity: error # Path Conventions ternary-path-kebab-case: description: Path segments must use kebab-case message: "Path segment in '{{property}}' must use kebab-case" given: "$.paths[*]~" then: function: pattern functionOptions: match: "^(/v1/[a-z0-9-/{}_]+)$" severity: warn ternary-collection-plural-nouns: description: Collection endpoints should use plural nouns message: "Collection path should use plural nouns" given: "$.paths[*]~" then: function: pattern functionOptions: notMatch: "^/v1/[a-z]+-[a-z]+$" severity: off # HTTP Methods ternary-list-uses-get: description: List operations must use GET method message: "List operations should use GET, not POST" given: "$.paths[*][get].operationId" then: function: pattern functionOptions: match: "^(list|get)[A-Z]" severity: warn ternary-create-uses-post: description: Create operations should use POST message: "Create operations should use POST" given: "$.paths[*][post].operationId" then: function: pattern functionOptions: match: "^(create|trigger|run|acknowledge)[A-Z]" severity: warn ternary-update-uses-put: description: Update operations should use PUT message: "Full update operations should use PUT" given: "$.paths[*][put].operationId" then: function: pattern functionOptions: match: "^update[A-Z]" severity: warn ternary-delete-uses-delete: description: Delete operations must use DELETE message: "Delete operations must use DELETE method" given: "$.paths[*][delete].operationId" then: function: pattern functionOptions: match: "^delete[A-Z]" severity: error # Response Codes ternary-list-response-200: description: List and GET operations must return 200 message: "GET operation must define a 200 response" given: "$.paths[*][get].responses" then: field: "200" function: truthy severity: error ternary-create-response-201: description: Create operations should return 201 message: "POST create operations should return 201 Created" given: "$.paths[*][post].responses" then: field: "201" function: truthy severity: warn ternary-delete-response-204: description: Delete operations should return 204 message: "DELETE operations should return 204 No Content" given: "$.paths[*][delete].responses" then: field: "204" function: truthy severity: warn ternary-401-defined: description: All operations must define a 401 Unauthorized response message: "Operation must define a 401 Unauthorized response" given: "$.paths[*][get,post,put,patch,delete].responses" then: field: "401" function: truthy severity: error ternary-404-for-resource-ops: description: Operations with path parameters should define 404 message: "Resource operations with path parameters should define 404 Not Found" given: "$.paths[*~'/{[^}]+}'][get,put,delete].responses" then: field: "404" function: truthy severity: warn # Pagination ternary-pagination-params: description: List endpoints should support pagination parameters message: "List endpoint should include page_token and page_size query parameters" given: "$.paths[*][get][?(@.operationId && @.operationId.startsWith('list'))]" then: field: parameters function: truthy severity: warn # Documentation ternary-operation-summary: description: All operations must have a summary message: "Operation '{{path}}' is missing a summary" given: "$.paths[*][get,post,put,patch,delete]" then: field: summary function: truthy severity: error ternary-summary-title-case: description: Operation summaries must use Title Case message: "Summary '{{value}}' must start with a capital letter (Title Case)" given: "$.paths[*][get,post,put,patch,delete].summary" then: function: pattern functionOptions: match: "^[A-Z]" severity: warn ternary-operation-description: description: All operations should have a description message: "Operation is missing a description" given: "$.paths[*][get,post,put,patch,delete]" then: field: description function: truthy severity: warn ternary-tags-defined: description: All operations must have tags message: "Operation is missing tags" given: "$.paths[*][get,post,put,patch,delete]" then: field: tags function: truthy severity: error # Schema Quality ternary-schema-description: description: Schema objects should have descriptions message: "Schema component '{{path}}' is missing a description" given: "$.components.schemas[*]" then: field: description function: truthy severity: warn ternary-no-inline-schemas: description: Prefer reusable schemas in components over inline definitions message: "Use $ref to reusable schemas instead of inline definitions" given: "$.paths[*][*].responses[*].content[*].schema" then: field: "$ref" function: truthy severity: warn # FinOps Domain Conventions ternary-cost-fields-numeric: description: Cost and amount fields should be numeric type message: "Cost/amount field should be type: number" given: "$.components.schemas[*].properties[?(@key =~ /cost|amount|spend|budget/i)]" then: field: type function: enumeration functionOptions: values: - number - integer severity: warn ternary-date-fields-format: description: Date fields should use ISO 8601 format message: "Date field should have format: date or format: date-time" given: "$.components.schemas[*].properties[?(@key =~ /date|_at$/i)]" then: field: format function: truthy severity: warn