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}/changesetsThe 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:
| Scope | Description |
|---|---|
read:domain | Read domain data (scans, changesets, traces) |
write:domain | Create/update domain data |
approve:agents | Accept changesets, promote agent configs |
admin:domain | Domain settings, API keys, invites |
admin:org | Org-level management |
read:agents | Fetch agent configuration (API key) |
write:traces | Push OTEL traces (API key) |
read:operations | Read operations-level data |
write:operations | Write operations-level data |
The scope resource names are governed by ADR-086; the structural shape (action:resource) stands.
5. Roles
Domain roles:
| Role | Scopes |
|---|---|
admin | read:domain, write:domain, approve:agents, admin:domain, read:agents |
contributor | read:domain, write:domain, read:agents |
observer | read:domain |
Org roles:
| Role | Scopes |
|---|---|
owner | admin:org + implicit admin-level domain access |
operations | All 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)