Unified API Design
3. Unified API Design¶
Document status: Draft v0.1 Depends on: sec1_overview.md, sec2_architecture.md, Hippo sec4 (REST API), Cappella sec5 (workflows), Canon sec2 (architecture) Feeds into: Bridge sec4 (auth path prefixes), Bridge sec5 (sync endpoints), Aperture sec2 (client integration)
3.1 Design Goals¶
Bridge exposes a single URL namespace that covers all BASS components. Clients make all requests to Bridge; Bridge routes to the appropriate component. This provides:
- One base URL to configure (instead of one per component)
- Consistent auth enforcement on every path
- Stable external API even as internal component topology changes
- A single place to add cross-cutting concerns (rate limiting, logging, versioning)
3.2 URL Namespace¶
All Bridge-managed endpoints are under /api/v1/. Component namespaces are assigned by
prefix:
| Prefix | Routes to | Example |
|---|---|---|
/api/v1/hippo/ |
Hippo REST API | GET /api/v1/hippo/entities/sample |
/api/v1/cappella/ |
Cappella REST API | POST /api/v1/cappella/runs |
/api/v1/canon/ |
Canon REST API | GET /api/v1/canon/resolve |
/api/v1/bridge/ |
Bridge-owned endpoints | POST /api/v1/bridge/auth/token |
Bridge strips the /api/v1/{component}/ prefix before forwarding to the component,
so components continue to use their own internal path schemes unchanged.
Bridge-owned paths (not forwarded to any component):
| Path | Purpose |
|---|---|
POST /api/v1/bridge/auth/token |
Issue token (Device Code callback, Client Credentials) |
POST /api/v1/bridge/auth/token/refresh |
Refresh access token |
POST /api/v1/bridge/auth/token/revoke |
Revoke access or refresh token |
GET /api/v1/bridge/auth/device |
Initiate Device Code flow (return device_code) |
POST /api/v1/bridge/auth/api-keys |
Create API key |
GET /api/v1/bridge/auth/api-keys |
List API keys (own, or all for admin) |
DELETE /api/v1/bridge/auth/api-keys/{keyId} |
Revoke API key |
POST /api/v1/bridge/auth/api-keys/{keyId}/rotate |
Rotate API key |
GET /api/v1/bridge/health |
Aggregated platform health |
GET /api/v1/bridge/metrics |
Prometheus metrics |
3.3 Routing Rules¶
3.3.1 Path Rewriting¶
Bridge applies a simple prefix-strip-and-forward rule:
Incoming: GET /api/v1/hippo/entities/sample?project=lab-a
Forwarded: GET /entities/sample?project=lab-a
→ http://hippo:8001/entities/sample?project=lab-a
No path transformation beyond prefix stripping. Component APIs are consumed as-is.
3.3.2 Header Injection¶
Before forwarding, Bridge adds the following headers:
| Header | Value | Purpose |
|---|---|---|
X-Bass-Actor |
Authenticated actor identity string | Component provenance |
X-Bass-Roles |
Comma-separated role list | Component auth middleware |
X-Bass-Request-Id |
UUIDv4 per request | Distributed tracing |
X-Forwarded-For |
Original client IP | Logging and rate limiting |
Bridge removes all inbound X-Bass-* headers from external clients before processing
to prevent header spoofing.
3.3.3 Response Passthrough¶
Bridge returns the component's response body and status code unchanged. No response transformation. Exceptions:
5xxfrom an unreachable component returns a structured Bridge error:- Auth failures (401, 403) are generated by Bridge before the component is contacted
and always include the
WWW-Authenticateheader.
3.4 Error Format¶
All Bridge-generated errors use a consistent JSON shape:
{
"error": "string", // machine-readable error code
"message": "string", // human-readable description
"request_id": "uuid", // matches X-Bass-Request-Id
"details": {} // optional structured details
}
Standard Bridge error codes:
| Code | HTTP Status | Meaning |
|---|---|---|
missing_token |
401 | No Authorization header or X-Api-Key present |
invalid_token |
401 | JWT signature invalid or malformed |
expired_token |
401 | JWT or API key has expired |
revoked_token |
401 | Token has been explicitly revoked |
insufficient_role |
403 | Authenticated actor lacks required role |
project_scope_denied |
403 | Actor is not a member of the requested project |
component_unavailable |
503 | Upstream component returned 5xx or is unreachable |
rate_limit_exceeded |
429 | Per-actor or per-IP rate limit hit |
route_not_found |
404 | No component registered for the requested prefix |
Component errors (4xx, 5xx from Hippo, Cappella, Canon) are passed through unchanged. Bridge does not rewrite component error bodies.
3.5 API Versioning¶
The /api/v1/ prefix represents the Bridge routing version, not component API versions.
Component APIs version independently within their own paths.
Bridge version guarantees:
- v1 routing rules are stable. Prefix-strip-and-forward semantics will not change in v1.
- New Bridge-owned endpoints are added under v1 with no breaking changes.
- Breaking routing changes (if any) will be introduced under /api/v2/ with a deprecation
period for v1.
3.6 Health Aggregation¶
GET /api/v1/bridge/health returns the health of the full platform:
{
"status": "healthy", // healthy | degraded | unhealthy
"bridge": "healthy",
"components": {
"hippo": {
"status": "healthy",
"latency_ms": 3,
"url": "http://hippo:8001"
},
"cappella": {
"status": "healthy",
"latency_ms": 5,
"url": "http://cappella:8002"
},
"canon": {
"status": "degraded",
"latency_ms": 800,
"url": "http://canon:8003",
"detail": "slow_response"
}
},
"checked_at": "2026-08-15T10:30:00Z"
}
Status rules:
- healthy: all components responding within their latency SLO
- degraded: one or more components responding slowly or returning non-5xx errors
- unhealthy: one or more components unreachable or returning 5xx
Health probes are configurable per component (path, timeout, interval). Results are cached
for a configurable TTL (default: 5 seconds) to prevent thundering-herd on /bridge/health.
3.7 Content Negotiation and Encoding¶
Bridge forwards Accept and Content-Type headers unchanged to components. It does not
perform content negotiation itself. Components are responsible for supporting the content
types they advertise.
Response streaming: Bridge uses httpx async streaming to forward large responses
(e.g., bulk Hippo entity exports) without buffering the full body in memory.
3.8 Cross-Origin Resource Sharing (CORS)¶
CORS headers are applied by Bridge (not components) for browser-based Aperture access:
# bridge.yaml
cors:
allowed_origins:
- https://bass.uni.edu # Aperture web portal
- http://localhost:3000 # Local dev
allowed_methods: [GET, POST, PUT, DELETE, PATCH, OPTIONS]
allowed_headers: [Authorization, Content-Type, X-Api-Key]
allow_credentials: true
max_age: 600
3.9 OpenAPI Documentation¶
Bridge generates a merged OpenAPI spec that includes:
- All Bridge-owned auth and management endpoints
- Component endpoint proxies (documented with their own schemas, fetched from component
/openapi.json at startup)
Available at GET /api/v1/openapi.json. Swagger UI at GET /api/v1/docs (development
mode only; disabled in production by default).
3.10 Open Questions¶
| Question | Priority | Status |
|---|---|---|
Should Bridge support path-based component versioning (e.g., /api/v1/hippo/v2/...)? |
Low | Open |
| Should the merged OpenAPI spec be cached or regenerated per request? | Low | Implementation detail; cache with TTL |
Add a GET /api/v1/bridge/components endpoint listing registered components and their versions? |
Medium | Open |