Skip to content
GitHub
Decisions

ADR-006: API Versioning Strategy and RESTful Conventions

ADR-006: API Versioning Strategy and RESTful Conventions

Context

This was a pre-launch API refactor — no external customers, so all breaking changes were safe to make.

The Spectral API had no consistent versioning strategy. Routes were a mix of unversioned (/api/scans, /api/auth), legacy versioned (/api/v2/objective-functions, /api/v2/anti-deception/check), and OTLP standard (/otel/v1/traces). Domain isolation was inconsistent: 19% of endpoints used domain_id in the URL path while 81% derived it from the X-Domain-Id header. Response envelopes varied across endpoints. 31 router files had overlapping concerns.

Decision

1. Versioning — out-of-band, no URL token

API versioning is date-based, key-pinned, and additive-only per ADR-091: the version is carried out-of-band rather than as an /api/v1/ URL-path prefix, so there is no version token in the URL path.

2. Domain ID in URL Path

All domain-scoped endpoints include domain_id in the URL:

/api/domains/{domain_id}/scans
/api/domains/{domain_id}/agents
/api/domains/{domain_id}/changesets

The X-Domain-Id header is still required for auth middleware validation but the URL path is the authoritative source for domain context. This makes URLs self-describing and enables better caching, logging, and debugging.

Non-domain routes (auth, org, health, operations, OTEL) remain at /api/ without a domain segment.

The org/domain resource names used in these paths are governed by ADR-086; the structural shape (domain_id in the path) stands.

3. Response Envelope

  • Collections: {"results": [...], "pagination": {"total": N, "limit": N, "offset": N}}
  • Single items: flat object (no wrapper)
  • Errors: RFC 9457 Problem Details

4. Scope Model: action:resource

9 scopes:

ScopeDescription
read:domainRead domain data (scans, changesets, traces)
write:domainCreate/update domain data
approve:agentsAccept changesets, promote agent configs
admin:domainDomain settings, API keys, invites
admin:orgOrg-level management
read:agentsFetch agent configuration (API key)
write:tracesPush OTEL traces (API key)
read:operationsRead operations-level data
write:operationsWrite operations-level data

The scope resource names are governed by ADR-086; the structural shape (action:resource) stands.

5. Roles

Domain roles:

RoleScopes
adminread:domain, write:domain, approve:agents, admin:domain, read:agents
contributorread:domain, write:domain, read:agents
observerread:domain

Org roles:

RoleScopes
owneradmin:org + implicit admin-level domain access
operationsAll scopes (Spectral staff)

The role layer names are governed by ADR-086; the structural shape stands.

6. Router structure

The customer-facing surface is POST /api/decide plus an MCP equivalent plus OpenAPI-style action discoverability (per ADR-077). The auth, orgs, domains, keys, and operations routes (enumerated in ADR-077 D6) use the versioning model of ADR-091 (date-based, key-pinned, carried out-of-band — paths are unversioned), domain-scoping (section 2), response envelope (section 3), scope model (section 4), and role model (section 5) established in this ADR.

7. Legacy Endpoint Removal

All /api/v2/* endpoints removed. Deprecated aliases (/api/scan, /api/run, /api/evaluate) also removed. Auth proxy routes live at /api/auth/ (non-domain-scoped, unversioned).

Consequences

Positive

Versioning consequences follow ADR-091: versioning is date-based and carried out-of-band, so there is no /api/v2/ path cutover. The §§2–5/7 consequences below stand.

  • Domain isolation enforced at URL level — no more header-only isolation
  • Response envelope standardized — clients can rely on consistent structure
  • Router consolidation reduces cognitive overhead and import chains

Negative

  • Breaking change for all existing API consumers (dashboard must update)
  • Test suite requires updates to new paths
  • OTEL ingestion endpoint path diverges from API versioning (by design, per OTLP standard)