# Censys API Spectral Ruleset # Generated 2026-05-29 from analysis of openapi/censys-platform-openapi.yml and openapi/censys-asset-graph-openapi.yml # Enforces Censys's OpenAPI 3.1, snake_case schema properties, /v{n} versioning, PersonalAccessToken bearer auth, # Personal Access Token security, and Title Case operation summaries prefixed with "Censys". extends: [[spectral:oas, recommended]] rules: # ── INFO / METADATA ──────────────────────────────────────────────── info-title-censys-prefix: description: API title must start with "Censys". given: $.info.title severity: warn then: function: pattern functionOptions: match: "^Censys" info-description-required: description: API info.description is required. given: $.info severity: warn then: field: description function: truthy info-version-required: description: API info.version is required. given: $.info severity: error then: field: version function: truthy # ── OPENAPI VERSION ──────────────────────────────────────────────── openapi-version-3-1: description: Censys APIs target OpenAPI 3.1. given: $ severity: warn then: field: openapi function: pattern functionOptions: match: "^3\\.1" # ── SERVERS ──────────────────────────────────────────────────────── servers-required: description: At least one server must be defined. given: $ severity: error then: field: servers function: truthy servers-https: description: Server URLs must use HTTPS. given: $.servers[*].url severity: error then: function: pattern functionOptions: match: "^https://" servers-censys-host: description: Server URL should be a censys.io domain. given: $.servers[*].url severity: warn then: function: pattern functionOptions: match: "censys\\.io" # ── PATHS ────────────────────────────────────────────────────────── paths-kebab-case: description: Path segments should be kebab-case (or path parameters). given: $.paths.*~ severity: warn then: function: pattern functionOptions: match: "^(/(v\\d+|api)?(/[a-z0-9-]+|/\\{[a-zA-Z0-9_]+\\})*)+/?$" paths-no-trailing-slash: description: Paths must not end with a trailing slash. given: $.paths.*~ severity: error then: function: pattern functionOptions: notMatch: ".+/$" paths-version-prefix: description: Censys paths must include a /v{n} or /api/v{n} version prefix. given: $.paths.*~ severity: warn then: function: pattern functionOptions: match: "^/(v\\d+|api/v\\d+)" # ── OPERATIONS ───────────────────────────────────────────────────── operation-operationid-required: description: Every operation must have an operationId. given: $.paths.*[get,post,put,patch,delete] severity: error then: field: operationId function: truthy operation-operationid-kebab: description: operationId should be kebab-case (Censys convention). given: $.paths.*[get,post,put,patch,delete].operationId severity: warn then: function: pattern functionOptions: match: "^[a-z0-9]+(-[a-z0-9]+)*$" operation-summary-required: description: Every operation must have a summary. given: $.paths.*[get,post,put,patch,delete] severity: error then: field: summary function: truthy operation-summary-title-case: description: Operation summary should be Title Case prefixed with "Censys". given: $.paths.*[get,post,put,patch,delete].summary severity: warn then: function: pattern functionOptions: match: "^Censys " operation-description-required: description: Every operation should have a description. given: $.paths.*[get,post,put,patch,delete] severity: warn then: field: description function: truthy operation-tags-required: description: Every operation must declare at least one tag. given: $.paths.*[get,post,put,patch,delete] severity: warn then: field: tags function: schema functionOptions: schema: type: array minItems: 1 # ── TAGS ─────────────────────────────────────────────────────────── tags-global-defined: description: Top-level tags array should be defined. given: $ severity: warn then: field: tags function: truthy tag-title-case: description: Tags should be Title Case (e.g. "Threat Hunting"). given: $.tags[*].name severity: warn then: function: pattern functionOptions: match: "^[A-Z][A-Za-z0-9]*( [A-Za-z0-9]+)*$" # ── PARAMETERS ───────────────────────────────────────────────────── parameter-description-required: description: Parameters should have a description. given: $.paths.*[get,post,put,patch,delete].parameters[*] severity: warn then: field: description function: truthy parameter-snake-case: description: Parameter names should be snake_case (Censys convention). given: $.paths.*[get,post,put,patch,delete].parameters[*].name severity: warn then: function: pattern functionOptions: match: "^[a-z][a-z0-9_]*$" parameter-no-auth-query: description: Authentication tokens must not be sent in query parameters. given: $.paths.*[get,post,put,patch,delete].parameters[?(@.in=='query')].name severity: error then: function: pattern functionOptions: notMatch: "(?i)(api[_-]?key|access[_-]?token|bearer)" # ── REQUEST BODIES ───────────────────────────────────────────────── requestbody-json-content: description: Request bodies must offer application/json content. given: $.paths.*[post,put,patch].requestBody.content severity: warn then: field: application/json function: truthy # ── RESPONSES ────────────────────────────────────────────────────── response-2xx-required: description: Operations must define at least one 2xx response. given: $.paths.*[get,post,put,patch,delete].responses severity: error then: function: schema functionOptions: schema: type: object patternProperties: "^2[0-9][0-9]$": {} minProperties: 1 response-401-defined: description: Authenticated operations should document a 401 response. given: $.paths.*[get,post,put,patch,delete].responses severity: warn then: field: '401' function: truthy response-429-defined: description: Operations should document a 429 rate-limit response. given: $.paths.*[get,post,put,patch,delete].responses severity: info then: field: '429' function: truthy response-json-content: description: 2xx responses should offer application/json content. given: $.paths.*[get,post,put,patch,delete].responses[?(@property.match(/^2[0-9][0-9]$/))].content severity: warn then: field: application/json function: truthy # ── SCHEMAS ──────────────────────────────────────────────────────── schema-property-snake-case: description: Schema properties must be snake_case. given: $.components.schemas.*.properties.*~ severity: warn then: function: pattern functionOptions: match: "^[a-z][a-z0-9_]*$" schema-type-required: description: Schemas must declare type or composition (oneOf/anyOf/allOf/$ref). given: $.components.schemas.* severity: warn then: function: schema functionOptions: schema: anyOf: - required: [type] - required: [oneOf] - required: [anyOf] - required: [allOf] - required: ["$ref"] # ── SECURITY ─────────────────────────────────────────────────────── security-personal-access-token-defined: description: PersonalAccessToken security scheme must be defined. given: $.components.securitySchemes severity: warn then: field: PersonalAccessToken function: truthy security-bearer-scheme: description: PersonalAccessToken must be HTTP bearer. given: $.components.securitySchemes.PersonalAccessToken severity: warn then: function: schema functionOptions: schema: type: object properties: type: { const: http } scheme: { const: bearer } required: [type, scheme] # ── GENERAL QUALITY ──────────────────────────────────────────────── deprecated-documented: description: Deprecated operations should document migration in description. given: $.paths.*[?(@.deprecated==true)] severity: warn then: field: description function: truthy