← Back to Platform Architecture
API Reference — Overview
Design Philosophy
The Stratum Platform API follows REST conventions with the following core principles:
Multi-Tenant Isolation — All endpoints filter queries by tenant_id from AuthContext. Cross-tenant access is forbidden.
Immutable Versioning — Updates never overwrite existing data. Version snapshots are stored immutably; rollback is always possible.
PHI Guard — Requests with PHI (SSN, MRN, phone, email) are rejected at middleware before reaching route handlers (422).
SUD Compliance — Substance Use Disorder data (42 CFR Part 2) has special sharing restrictions; cannot be exported with ?deidentify=true without attorney review.
Audit Everything — All data access and modification events are logged with user_id, tenant_id, timestamp, and event type for HIPAA compliance.
Credit Integration — Query costs (marketplace access) deduct from tenant balance via event-sourced ledger; no credit = 402 Payment Required.
API Versioning
Current Version: v1
Pattern: Endpoints use /api/ prefix without explicit version number (implicitly v1).
Future Versioning Strategy (Roadmap):
- If breaking changes are introduced, new endpoints will use
/api/v2/
- v1 endpoints will remain available for 12 months (deprecation window)
- Clients will be notified 90 days before v1 sunsetting
Environments
| Environment | API Base URL | Frontend URL | Database | Notes |
| Production | https://stratum-platform-api.fly.dev | https://platform.stratumcollective.co | Fly Postgres | Live data, real tenants |
| Staging | TBD | TBD | TBD | Future |
| Local | http://localhost:3001 | http://localhost:3088 | Docker Postgres | Dev/testing |
HTTP Status Codes
Success (2xx)
- 200 OK — Request succeeded, returning data (GET, PUT, POST with response body)
- 201 Created — POST succeeded, resource created (returns new resource)
- 204 No Content — Request succeeded, no response body (DELETE soft-delete, bulk operations)
Client Error (4xx)
- 400 Bad Request — Malformed request (invalid JSON, missing required fields, invalid parameter types). Details in response.
- 401 Unauthorized — Authentication failed (missing X-User-Id header, header value doesn't resolve to valid user).
- 402 Payment Required — Insufficient credits for marketplace query. Response includes balance, cost, shortfall.
- 403 Forbidden — Authenticated but not authorized (insufficient role, cross-tenant access, SUD export without admin role).
- 404 Not Found — Resource does not exist or is archived.
- 422 Unprocessable Entity — Request syntax valid but business rule violation (PHI detected, SUD compliance violation, validation gate failure).
Server Error (5xx)
- 500 Internal Server Error — Unexpected server error. Logged internally; contact support.
Request/Response Envelope
Standard Request Headers
X-User-Id: <user-uuid> # REQUIRED (Phase 1 auth)
Content-Type: application/json
Standard Response Envelope
Success Response (200, 201):
{
"data": { ... } or [ ... ],
"total": 1000,
"limit": 20,
"offset": 0,
"has_more": true
}
Error Response (4xx, 5xx):
{
"status": "error",
"type": "validation_failed| phi_detected | sud_compliance_violation | insufficient_credits | data_error | not_found |
unauthorized",
"message": "Human-readable error message",
"details": { ... }
}
Pagination
All list endpoints support limit/offset pagination with total count:
Query Parameters
- limit (default: 20, max: 100) — Number of records to return
- offset (default: 0) — Number of records to skip from start
Response Fields
- data — Array of records (0 to limit items)
- total — Total record count (used for pagination UI, e.g., "Showing 20–40 of 1000")
- limit — Echoed limit from request
- offset — Echoed offset from request
- has_more — Boolean, true if offset + limit < total (convenience for UI)
Example
GET /api/precedents?limit=20&offset=40
Response:
{
"data": [ ... 20 precedents ... ],
"total": 1000,
"limit": 20,
"offset": 40,
"has_more": true
}
Sorting and Filtering
Sorting:
sort_by — Field name (e.g., "created_at", "outcome")
sort_direction — "asc" or "desc" (default: "asc" for text, "desc" for dates)
Filtering:
Exact match: ?field=value
Case-insensitive text: ?payer=cigna (backend does ILIKE match)
JSONB queries: ?policy_reference.section=2.3.1 (use dot notation for nested fields)
Range queries: ?created_after=2024-01-01&created_before=2024-12-31 (ISO 8601 dates)
Examples:
GET /api/precedents?sort_by=created_at&sort_direction=desc
GET /api/precedents?payer=Cigna&outcome=Won
GET /api/precedents?created_after=2024-04-01&limit=50&offset=0
Tenant Context
All endpoints require X-User-Id header to establish tenant context:
Header Lookup: X-User-Id: <user-uuid>
User Resolution: Backend queries users table to find user_id → tenant_id mapping
Tenant Filtering: All subsequent queries filtered by tenant_id = resolved_tenant_id
Cross-Tenant Access: Forbidden. If user A tries to access tenant B's resources, returns 403.
Phase 1 Authentication: Direct user_id lookup (not production-ready).
Phase 2 Roadmap: Auth0/Clerk JWT tokens with role claims.
Error Handling
All error responses include:
status — "error"
type — Error category (for client-side handling)
message — Human-readable explanation
details — Additional context (validation errors, field-level issues, etc.)
Example: PHI Rejection
{
"status": "error",
"type": "phi_detected",
"message": "Protected Health Information detected: phone",
"details": {
"offending_identifier": "phone",
"guidance": "Remove phone numbers, SSNs, MRNs from request body and re-submit"
}
}
Example: Validation Error
{
"status": "error",
"type": "validation_failed",
"message": "Validation failed",
"details": [
{
"path": "narrative",
"message": "String must contain at least 50 characters"
}
]
}
Credit Integration
Marketplace queries deduct from tenant's credit balance:
Query Cost Calculation:
cost = 1 + (precedent_count_in_cluster / 10 × 0.5)
Examples:
10-precedent cluster → 1.5 credits
50-precedent cluster → 3.5 credits
100-precedent cluster → 6 credits
Balance Check: Before query, backend sums all credit_ledger entries for tenant. If balance < cost, returns 402 with details.
Ledger Logging: On successful query, appends entry to credit_ledger:
{
"event_type": "query_burn",
"amount": -3.5,
"tenant_id": "...",
"reference_id": "query-uuid",
"receipt_hash": "..."
}
No Balance Column: Balance is always calculated from ledger (never stored), ensuring audit accuracy.
Soft Deletes
The platform uses soft deletes (status='Archived') rather than hard deletes:
DELETE /api/precedents/:id → Sets archived=true, marks archived_at=NOW()
GET /api/precedents → By default excludes archived records (unless ?include_archived=true)
GET /api/precedents/:id → 404 if archived (unless called with ?include_archived=true)
Rationale: Audit trail, compliance (HIPAA 7-year retention), and appeal recovery.
Compliance & Security
PHI Validation: Middleware scans all request bodies for 18 identifiers (SSN, name, address, phone, email, MRN, etc.). Rejected with 422 before reaching route handler.
SUD Compliance: Substance Use Disorder data (42 CFR Part 2) cannot be exported with ?deidentify=true unless explicitly authorized. SUD data marked data_origin_type='SUD' with sharing_restrictions='consent_required' or 'originator_only'.
Deidentification: GET ?deidentify=true applies HIPAA Safe Harbor (45 CFR §164.514(b)) removing all 18 identifiers. Admin-only. Logged with audit attestation.
Audit Trail: All access/modification logged to precedent_audit_log and appeals_audit_log with user_id, tenant_id, timestamp, event_type.
7-Year Retention: All audit logs retained per HIPAA guidelines. Cleanup job runs monthly to archive/delete records beyond 7-year window.
Rate Limiting
Current Status: Not yet implemented (Phase 1).
Roadmap (v2):
Per-tenant rate limiting: 100 requests/minute
Burst allowance: 20 requests in 1-second window
Marketplace queries not rate-limited (credit cost controls usage)
See Also
AUTHENTICATION.md — X-User-Id header, Auth0 roadmap
ERROR-HANDLING.md — Detailed error types and responses
PAGINATION-FILTERING.md — Sorting, filtering, cursor pagination roadmap