ADR-026 — API completeness & OpenAPI policy
1. Context
Noxys product narrative includes "API-first" but execution has drifted:
noxys-api/internal/docs/openapi.yamlexists but is stale (architecture-debt audit 2026-04-11 §6 flagged it).- 9 ADRs filed today (014-024) define ~50 new endpoints across subscriptions, storage, lineage, MFA, token reveal, entitlements, AI cost, devices, compliance reports, agent versions. None have OpenAPI spec entries.
- No CI gate prevents endpoint additions without spec.
- noxys-console consumes
lib/api.tswith hand-written types — recurring shape-mismatch bugs (PRs #167-#173) trace back partly to this. - No SDK (Python / JS) ships. Roadmap row [#184] mentions SDK auto-gen but blocked on stable spec.
- No public API docs portal — "API-first" claim has no surface to back it.
Without a binding policy, every new sprint will drift further. We must fix the base before ADR-014..024 implementation expands the gap.
2. Decision summary
- OpenAPI 3.1 is the canonical API specification format.
- Single source of truth = Go handlers + struct tags (in noxys-api). The
openapi.yamlfile is GENERATED, never hand-edited. - Generation tool:
swaggo/swag(Go-native, struct-tag driven, mature). Alternativekin-openapifor parsing/validation only. - Hard rule: every PR introducing a new endpoint OR modifying request/response schemas MUST include an updated
openapi.yaml. CI gate enforces this. - Console typed-client:
openapi-typescriptgenerates TS types from openapi.yaml; consumed bynoxys-console/src/lib/api.tsto replace hand-written shapes. - SDK auto-gen:
openapi-generatorproduces Python + JS clients on each release tag. Lives innoxys-contractsrepo (existing). - Versioning: API path-versioned
/api/v1/.... Breaking changes →/api/v2/...with 6-month deprecation overlap. Non-breaking additions OK in v1. - Public docs: Redoc-rendered
openapi.yamldeployed atapi.noxys.eu(ornoxys.eu/api) post-Pro launch (W27-28). - Backfill obligation: ADRs 014-024 endpoints inventoried + spec'd before implementation sprints land code.
3. Single source of truth — Go handler annotations
// CreatePolicy creates a new policy for the tenant.
//
// @Summary Create policy
// @Description Create a tenant-scoped policy with conditions, actions, priority.
// @Tags policies
// @Accept json
// @Produce json
// @Param body body CreatePolicyRequest true "Policy spec"
// @Success 201 {object} Policy
// @Failure 400 {object} Error
// @Failure 403 {object} Error
// @Router /api/v1/policies [post]
// @Security BearerAuth
func (h *Handler) CreatePolicy(w http.ResponseWriter, r *http.Request) { ... }
type CreatePolicyRequest struct {
Name string `json:"name" validate:"required" example:"Block PII"`
Description string `json:"description,omitempty"`
Conditions []Condition `json:"conditions" validate:"required"`
Action string `json:"action" validate:"oneof=log redact block" example:"block"`
Priority int `json:"priority" validate:"min=0,max=100"`
Enabled bool `json:"enabled"`
}
make openapi-gen runs swag init -g cmd/api/main.go -o internal/docs/. Output openapi.yaml (and Swagger UI assets) updated atomically.
4. CI gate
# .github/workflows/openapi-validate.yml (noxys-api)
name: OpenAPI integrity
on: [pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- run: go install github.com/swaggo/swag/cmd/swag@latest
- run: make openapi-gen
- name: Fail if openapi.yaml drifted
run: |
if ! git diff --exit-code internal/docs/openapi.yaml; then
echo "::error::openapi.yaml is out of date. Run 'make openapi-gen' and commit."
exit 1
fi
- run: npx -y @redocly/cli lint internal/docs/openapi.yaml
Pre-commit hook (developer convenience):
# .githooks/pre-commit
#!/bin/sh
if git diff --cached --name-only | grep -qE '\.go$'; then
make openapi-gen >/dev/null 2>&1
git add internal/docs/openapi.yaml
fi
5. Console typed-client codegen
// noxys-console/package.json
{
"scripts": {
"openapi:fetch": "curl -fsSL https://api-staging.noxys.cloud/api/v1/openapi.yaml -o src/lib/openapi.yaml",
"openapi:gen": "openapi-typescript src/lib/openapi.yaml -o src/lib/openapi.types.ts"
},
"devDependencies": {
"openapi-typescript": "^7"
}
}
src/lib/api.ts becomes a thin wrapper around fetch that imports types from openapi.types.ts — all request/response shapes type-safe across full stack.
CI hook in noxys-console: fail if openapi.types.ts drift (regen + diff check) so frontend types track backend automatically.
6. SDK auto-gen (Python + JS)
noxys-contracts repo:
# .github/workflows/sdk-release.yml
on:
push:
tags: ['v*']
jobs:
python-sdk:
steps:
- uses: openapitools/openapi-generator-cli-action@v1
with:
generator: python
input: openapi.yaml
output: sdks/python
- run: cd sdks/python && python -m build
- run: twine upload dist/* --repository pypi
js-sdk:
steps:
- uses: openapitools/openapi-generator-cli-action@v1
with:
generator: typescript-fetch
input: openapi.yaml
output: sdks/js
- run: cd sdks/js && npm publish
Published as noxys-sdk on PyPI + @noxys/sdk on npm.
7. Public docs portal
Redoc-rendered static site:
<!-- api.noxys.eu/index.html (or noxys.eu/api) -->
<redoc spec-url="https://api.noxys.cloud/api/v1/openapi.yaml"></redoc>
Deployed via Cloudflare Pages with auto-deploy on noxys-contracts main branch (mirrors openapi.yaml from noxys-api).
Tier gating: full spec public Pro+ endpoints; Enterprise-only endpoints documented but flagged x-tier: enterprise in spec; access-controlled at API key level.
8. Versioning policy
- Path-based:
/api/v1/...,/api/v2/.... - Non-breaking changes (added field, optional param, new endpoint) → stay in v1.
- Breaking changes (removed field, type change, required-param addition) → bump v2 + 6-month deprecation overlap on v1.
- Version sunset announced via Trust Center + email to API key holders 90d before.
Sunsetheader on deprecated endpoint responses (RFC 8594).
9. Backfill obligation — ADR 014-024 endpoints
The following endpoints (defined in ADRs but no OpenAPI yet) MUST be added to openapi.yaml before implementation sprints touch code:
- ADR-014 AI-PLACE-* —
/api/v1/copilot/ask,/api/v1/policies/semantic-eval,/api/v1/redaction/preview - ADR-018 SUB-* —
/api/v1/subscriptions/*,/api/v1/subscriptions/:id/events,/api/v1/tenants/:id/subscription - ADR-019 STORE-* —
/api/v1/storage/tiers,/api/v1/tenants/:id/region,/api/v1/tenants/:id/export,/api/v1/tenants/:id/erase - ADR-020 LINEAGE-* —
/api/v1/interactions/:id/lineage,/api/v1/lineage/steps(ingest) - ADR-022 USERS-* —
/api/v1/users/:id/entitlements,/api/v1/users/:id/cost - ADR-024 Auth+MFA —
/auth/mfa/totp/setup,/auth/mfa/totp/verify,/auth/mfa/webauthn/begin,/auth/mfa/webauthn/finish,/auth/mfa/backup-codes,/api/v1/tenants/:id/mfa-policy,/api/v1/tenant-tokens(CRUD + reveal) - AGENT-VER-1 —
/api/v1/agent/release-info,/api/v1/extension/release-info - TENANT-TOKEN-REVEAL-1 —
/api/v1/tenant-tokens/:id/reveal
Sprint API-OPENAPI-4 owns this backfill.
10. Sprint plan — 7 sprints, 9.75 days
| ID | Title | Repo | Window | Effort | Priority |
|---|---|---|---|---|---|
| API-OPENAPI-1 | Regen baseline openapi.yaml from current state + swaggo annotations + Makefile target | noxys-api | W21-22 | 1d | P0 |
| API-OPENAPI-2 | CI gate "no endpoint without spec" + pre-commit hook | noxys-api | W23-24 | 1d | P1 |
| API-OPENAPI-3 | openapi-typescript codegen in noxys-console; replace hand-written types in api.ts | noxys-console | W25-26 | 1.5d | P1 |
| API-OPENAPI-4 | Backfill ADR 014-024 endpoints in openapi.yaml (subscriptions, storage, lineage, MFA, etc.) | noxys-api | W23-24 | 2d | P0 |
| API-OPENAPI-5 | SDK Python + JS auto-gen via openapi-generator (closes [#184]) | noxys-contracts | W27-28 | 3d | P2 |
| API-OPENAPI-6 | Public Redoc docs at api.noxys.eu + Cloudflare Pages deploy | noxys-website | W29-30 | 1d | P2 |
| API-OPENAPI-7 | Memory feedback rule + noxys-api/CLAUDE.md update + commit hook activation | noxys-api | W21-22 | 0.25d | P0 |
P0 sprints (1, 4, 7) target W21-22 to fix base immediately. Existing related issues:
- [#24] OpenAPI 3.1 regeneration → covered by API-OPENAPI-1; comment-ref
- [#212] API publique REST v1 → covered by API-OPENAPI-1+2+5+6 combined; comment-ref
- [#184] SDK auto-gen → covered by API-OPENAPI-5; close as duplicate-by-replacement
11. Memory rule (binding for all future ADRs / sprints)
Add to ~/.claude/projects/-home-jeromesoyer-Documents-Github-noxys/memory/:
feedback rule: Every new API endpoint defined in an ADR or sprint MUST include OpenAPI 3.1 spec entries in the same PR. CI gate enforces. Reject any feature proposal that introduces an endpoint without spec. Pre-implementation, the endpoint signature lands in
openapi.yamlfirst; implementation follows.
12. Tier exposure rules
- Free tier: read-only endpoints + tenant config CRUD.
- Pro: full read/write + analytics endpoints.
- Enterprise: + admin endpoints + bulk endpoints + region pin override + raw storage access.
- Spec carries
x-tier: free|pro|enterpriseextension on each operation; API gateway enforces at runtime. - Public docs render shows tier badge per operation; non-subscribed tier dimmed.
13. Non-goals
- gRPC public API (kept internal sidecar protocol).
- GraphQL (REST-only public surface; complexity unjustified at current scale).
- API throttling beyond per-tier rate limits (deferred to Cloudflare API Gateway integration, separate sprint).
- Multi-tenant API key federation (Enterprise SSO covers it).
14. Open questions
- swaggo vs alternative codegen: swaggo is Go-idiomatic but limited expressiveness. If we hit limitations, fallback to manual
openapi.yamlauthoring + use kin-openapi for runtime validation. Decide if/when blocker hit. - Spec hosting cache: openapi.yaml served from API server or static CDN? Recommend CDN for public; serve from API for authenticated tier-aware view (with tenant-specific endpoint visibility).
- Backwards compat for noxys-extension API calls: extension uses
/extension/*non-versioned routes today. Add to spec asv1with deprecation notice if naming changes needed. Defer rename to V2.
15. Refs
- noxys-api
internal/docs/openapi.yaml(existing, stale) - noxys-contracts repo (protobuf + OpenAPI home)
- Architecture-debt audit 2026-04-11 §6 (OpenAPI stale flag)
- Roadmap [#24], [#212], [#184], [#510]
- ADRs 014, 018, 019, 020, 022, 024 — endpoint sources for backfill
- Future ADR-027 CRM choice (renumbered through ADR-019..ADR-026)