Skip to content

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:

  • 5xx from an unreachable component returns a structured Bridge error:
    { "error": "component_unavailable", "component": "hippo", "upstream_status": 503 }
    
  • Auth failures (401, 403) are generated by Bridge before the component is contacted and always include the WWW-Authenticate header.

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