Openwork · API Governance Rules

Openwork — openwork rules

Spectral linting rules defining API design standards and conventions for Openwork.

5 Rules error 2 warn 3
Published by Openwork Served by the provider at https://github.com/different-ai/openwork/blob/70c948c2abfc9b875c2971c6306c1603ac62f587/.spectral.yaml; the copy below was fetched from there.
View Rules File View on GitHub

Rule Categories

ow

Rules

error
ow-operation-security
Every operation must declare `security` (an empty array marks a public route).
$.paths[*][get,post,put,patch,delete,head,options,trace]
error
ow-operation-has-4xx
Protected operations must document at least one 4xx response.
$.paths[*][get,post,put,patch,delete][?(@.security && @.security[0])].responses
warn
ow-200-has-content
200 responses must document their body; use 204 for empty responses.
$.paths[*][*].responses['200']
warn
ow-timestamps-date-time
Properties ending in `At` must be RFC 3339 `string` / `date-time`.
$..properties[?(@ && @property.match(/At$/) && !@.anyOf && !@.oneOf && !@.$ref)]$..properties[?(@property.match(/At$/))].anyOf[?(@ && @.type != 'null')]
warn
ow-list-pagination
List GET operations must accept `cursor` + `limit` and return `nextCursor`.
$.paths[*].get

Spectral Ruleset

Raw ↑
# harvested from https://github.com/different-ai/openwork/blob/70c948c2abfc9b875c2971c6306c1603ac62f587/.spectral.yaml on 2026-10-09 — a Spectral ruleset published in the provider's own GitHub repository (different-ai/openwork); found by GitHub code search, fetched verbatim
x-method: harvested
x-stamped: 2026-10-09
x-source-url: https://github.com/different-ai/openwork/blob/70c948c2abfc9b875c2971c6306c1603ac62f587/.spectral.yaml
# Spectral ruleset for the Den API OpenAPI document (packages/docs/openapi.json).
#
# This is the single API-quality gate: `pnpm api:lint` regenerates nothing, it
# lints the committed snapshot; CI regenerates first (drift check) and then
# lints. Errors fail the build; warnings are ratcheted against
# .spectral-baseline.json and may not increase.
#
# Rule intent, exemptions and the protocol-adapter boundary are documented in
# docs/api-style.md. Keep the two files in sync.

extends: [[spectral:oas, recommended]]

rules:
  # ---- spectral:oas severity adjustments -------------------------------------
  operation-tag-defined: error
  operation-tags: error
  operation-description: warn
  oas3-unused-component: warn
  info-contact: error
  # prefixItems tuples are valid OpenAPI 3.1 / JSON Schema 2020-12 but the
  # bundled array-items rule still expects `items`; keep the check without
  # failing the build on the two tuple schemas.
  array-items: warn

  # ---- OpenWork house rules ---------------------------------------------------

  # Every operation states how it is authenticated. `security: []` is the
  # explicit marker for a public route; omitting the key is not allowed even
  # though document-level security would apply, because generators and
  # scanners commonly ignore the document-level default. Routes without a
  # describeRoute() surface here.
  ow-operation-security:
    description: Every operation must declare `security` (an empty array marks a public route).
    message: "{{path}} has no `security` array; declare one in describeRoute() (use [] for public routes)."
    severity: error
    given: "$.paths[*][get,post,put,patch,delete,head,options,trace]"
    then:
      field: security
      function: defined

  # Protected operations (non-empty security) document at least one 4xx so
  # clients learn the failure envelope. Public health/metadata routes are
  # exempt through their empty security array.
  ow-operation-has-4xx:
    description: Protected operations must document at least one 4xx response.
    message: "{{path}} is protected but documents no 4xx response."
    severity: error
    given: "$.paths[*][get,post,put,patch,delete][?(@.security && @.security[0])].responses"
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          anyOf:
            - required: ["400"]
            - required: ["401"]
            - required: ["402"]
            - required: ["403"]
            - required: ["404"]
            - required: ["405"]
            - required: ["406"]
            - required: ["408"]
            - required: ["409"]
            - required: ["410"]
            - required: ["412"]
            - required: ["413"]
            - required: ["415"]
            - required: ["422"]
            - required: ["423"]
            - required: ["428"]
            - required: ["429"]

  # A 200 without `content` leaves the response body undocumented. Use 204 when
  # there is genuinely no body.
  ow-200-has-content:
    description: 200 responses must document their body; use 204 for empty responses.
    message: "{{path}} is a 200 with no `content`; document the body or return 204."
    severity: warn
    given: "$.paths[*][*].responses['200']"
    then:
      field: content
      function: truthy

  # Timestamps end in `At` and are RFC 3339 `date-time` strings. Nullable
  # timestamps are expressed as anyOf [date-time, null]; the second `given`
  # checks the non-null branch.
  ow-timestamps-date-time:
    description: Properties ending in `At` must be RFC 3339 `string` / `date-time`.
    message: "{{property}} ends in `At` but is not a string with format date-time (see docs/api-style.md#timestamps)."
    severity: warn
    given:
      - "$..properties[?(@ && @property.match(/At$/) && !@.anyOf && !@.oneOf && !@.$ref)]"
      - "$..properties[?(@property.match(/At$/))].anyOf[?(@ && @.type != 'null')]"
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          properties:
            type:
              const: string
            format:
              const: date-time
          required: [type, format]

  # Collections paginate with cursor + limit and return nextCursor. A GET whose
  # response carries `items` or that accepts `limit`/`offset` is a list.
  ow-list-pagination:
    description: List GET operations must accept `cursor` + `limit` and return `nextCursor`.
    message: "{{path}} looks like a list but does not implement cursor + limit + nextCursor pagination (see docs/api-style.md#pagination)."
    severity: warn
    given: "$.paths[*].get"
    then:
      function: schema
      functionOptions:
        schema:
          # A GET is a list when it accepts limit/offset or returns `items`.
          if:
            anyOf:
              - required: [parameters]
                properties:
                  parameters:
                    contains:
                      type: object
                      required: [in, name]
                      properties:
                        in: { const: query }
                        name: { enum: [limit, offset] }
              - required: [responses]
                properties:
                  responses:
                    required: ["200"]
                    properties:
                      "200":
                        required: [content]
                        properties:
                          content:
                            required: [application/json]
                            properties:
                              application/json:
                                required: [schema]
                                properties:
                                  schema:
                                    required: [properties]
                                    properties:
                                      properties:
                                        required: [items]
          then:
            required: [parameters, responses]
            properties:
              parameters:
                allOf:
                  - contains:
                      type: object
                      required: [in, name]
                      properties: { in: { const: query }, name: { const: cursor } }
                  - contains:
                      type: object
                      required: [in, name]
                      properties: { in: { const: query }, name: { const: limit } }
              responses:
                required: ["200"]
                properties:
                  "200":
                    required: [content]
                    properties:
                      content:
                        required: [application/json]
                        properties:
                          application/json:
                            required: [schema]
                            properties:
                              schema:
                                required: [properties]
                                properties:
                                  properties:
                                    required: [nextCursor]

# ---- Documented exceptions ----------------------------------------------------
# Protocol adapters follow their RFC verbatim and are exempt from house naming,
# pagination and timestamp rules (they still need security, summary, tags and
# responses). Pointers are JSON-pointer escaped (`/` -> `~1`) and, as Spectral
# does, percent-encode path parameter braces (`{` -> `%7B`, `}` -> `%7D`).
overrides:
  # Audit export is a bounded NDJSON/CSV attachment, not a JSON list envelope.
  # It accepts cursor + limit and returns X-Audit-Next-Cursor until exhausted;
  # X-Audit-Snapshot-Sequence pins the committed snapshot. Route/schema and
  # transport tests verify these headers survive the API and browser proxy.
  - files:
      - "**/*.json#/paths/~1v1~1audit~1export"
    rules:
      ow-list-pagination: "off"

  # SCIM 2.0 (RFC 7643/7644): PascalCase resources, startIndex/count paging,
  # `meta.lastModified` timestamps.
  - files:
      - "**/*.json#/paths/~1api~1auth~1scim~1v2~1Schemas"
      - "**/*.json#/paths/~1api~1auth~1scim~1v2~1Schemas~1urn:ietf:params:scim:schemas:core:2.0:Group"
      - "**/*.json#/paths/~1api~1auth~1scim~1v2~1ResourceTypes"
      - "**/*.json#/paths/~1api~1auth~1scim~1v2~1ResourceTypes~1Group"
      - "**/*.json#/paths/~1api~1auth~1scim~1v2~1Groups"
      - "**/*.json#/paths/~1api~1auth~1scim~1v2~1Groups~1%7BgroupId%7D"
      - "**/*.json#/paths/~1api~1auth~1scim~1v2~1Users"
      - "**/*.json#/paths/~1api~1auth~1scim~1v2~1Users~1%7BuserId%7D"
    rules:
      ow-list-pagination: "off"
      ow-timestamps-date-time: "off"
      operation-description: "off"

  # OAuth 2.0 / OIDC discovery (RFC 8414, OIDC Discovery), dynamic client
  # registration (RFC 7591, snake_case fields) and protected-resource metadata
  # (RFC 9728): public by specification, bodies defined by the RFCs.
  - files:
      - "**/*.json#/paths/~1.well-known~1oauth-authorization-server"
      - "**/*.json#/paths/~1.well-known~1oauth-authorization-server~1api~1auth"
      - "**/*.json#/paths/~1.well-known~1openid-configuration"
      - "**/*.json#/paths/~1.well-known~1openid-configuration~1api~1auth"
      - "**/*.json#/paths/~1api~1auth~1.well-known~1oauth-authorization-server"
      - "**/*.json#/paths/~1api~1auth~1.well-known~1openid-configuration"
      - "**/*.json#/paths/~1register"
      - "**/*.json#/paths/~1api~1auth~1oauth2~1register"
      - "**/*.json#/paths/~1api~1auth~1oauth2~1authorize"
      - "**/*.json#/paths/~1.well-known~1oauth-protected-resource"
      - "**/*.json#/paths/~1.well-known~1oauth-protected-resource~1mcp"
      - "**/*.json#/paths/~1.well-known~1oauth-protected-resource~1mcp~1agent"
      - "**/*.json#/paths/~1.well-known~1oauth-protected-resource~1mcp~1admin"
      - "**/*.json#/paths/~1mcp~1.well-known~1oauth-protected-resource"
      - "**/*.json#/paths/~1mcp~1agent~1.well-known~1oauth-protected-resource"
      - "**/*.json#/paths/~1mcp~1admin~1.well-known~1oauth-protected-resource"
      - "**/*.json#/paths/~1oauth~1client-metadata.json"
    rules:
      ow-list-pagination: "off"
      ow-timestamps-date-time: "off"
      ow-operation-has-4xx: "off"

  # Liveness probes have no failure envelope beyond the status code.
  - files:
      - "**/*.json#/paths/~1health"
      - "**/*.json#/paths/~1ready"
    rules:
      ow-operation-has-4xx: "off"

  # Deprecated tombstones intentionally answer only 410 (skill hubs, memory
  # save). DELETE /v1/memory/{id} answers 401/404 only and is listed here for
  # the same reason; whether it should also document a 2xx is tracked as a
  # follow-up in docs/api-style.md.
  - files:
      - "**/*.json#/paths/~1v1~1skill-hubs"
      - "**/*.json#/paths/~1v1~1skill-hubs~1%7BskillHubId%7D"
      - "**/*.json#/paths/~1v1~1skill-hubs~1%7BskillHubId%7D~1skills"
      - "**/*.json#/paths/~1v1~1skill-hubs~1%7BskillHubId%7D~1skills~1%7BskillId%7D"
      - "**/*.json#/paths/~1v1~1skill-hubs~1%7BskillHubId%7D~1access"
      - "**/*.json#/paths/~1v1~1skill-hubs~1%7BskillHubId%7D~1access~1%7BaccessId%7D"
      - "**/*.json#/paths/~1v1~1memory"
      - "**/*.json#/paths/~1v1~1memory~1%7Bid%7D"
    rules:
      operation-success-response: "off"

  # Where a member's Automations can run: their own desktops, capped by
  # DESKTOP_TARGET_LIST_LIMIT, plus the cloud flag. A bounded snapshot, not a
  # paged collection. lastSeenAt is epoch milliseconds like every other
  # Automation timestamp (AutomationRun, Automation, AutomationRevision).
  - files:
      - "**/*.json#/paths/~1v1~1automation-runners"
    rules:
      ow-list-pagination: "off"
  # Distinct analytics dimension values (project labels) for one selector:
  # bounded by the organization's labels and read whole by the desktop and Den
  # web, so it is not a paginated collection.
  - files:
      - "**/*.json#/paths/~1v1~1telemetry~1dimensions"
    rules:
      ow-list-pagination: "off"
  - files:
      - "**/*.json#/components/schemas/AutomationDesktopTarget/properties/lastSeenAt"
    rules:
      ow-timestamps-date-time: "off"

Work with this as data

Every ruleset here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for spectral rules

4 MCP tools reach this
  • find_rulesBrowse and filter every ruleset in the catalog.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This ruleset
curl "https://apis.io/api/v1/rules/openwork-openwork-spectral-rules"
All spectral rules
curl "https://apis.io/api/v1/rules?limit=25"

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.

A second provider on the same verified email joins the account you already have.