← Back to Platform Architecture
Endpoints — Precedents
Core CRUD, versioning, and export endpoints for managing precedent objects.
POST /api/precedents
Create a new precedent object.
Authentication: Required (X-User-Id header)
Authorization: admin, biller roles only
PHI Validation: All request body scanned for 18 identifiers; rejected with 422 if found
Request Body
{
"precedent_name": "Cigna IOP Frequency Limit Victory 2024",
"payer": "Cigna",
"denial_type": "Frequency Limit",
"loc": "IOP",
"status": "Complete",
"policy_reference": {
"document_url": "https://cigna-intranet/policy-2024.pdf",
"policy_section": "Section 2.3.1",
"exact_language": "Frequency limit: 60 days per benefit year for IOP. Review required after 60 days."
},
"narrative": "Patient with GAF score 35 demonstrates ongoing need for IOP-level care. Clinical deterioration imminent without continued treatment.",
"denial_trigger": "Patient exceeded 60-day frequency limit (day 64 of benefit year)",
"evidence_kit": {
"required": ["Clinical progress notes (past 30 days)", "Psychiatric evaluation"],
"recommended": ["Collateral contact documentation"],
"optional": ["Prior treatment records"]
},
"outcome": "Won",
"time_to_resolution_days": 14,
"traceability_ledger": [
{ "event_type": "denial_received", "date": "2024-04-01", "notes": "Denial received via EDI" },
{ "event_type": "appeal_filed", "date": "2024-04-03", "notes": "Appeal submitted with clinical justification" },
{ "event_type": "approved", "date": "2024-04-15", "notes": "Appeal approved, 14 days to resolution" }
]
}
Response (201 Created)
{
"id": "prec-uuid",
"cluster_id": "Cigna | Frequency Limit |
IOP",
"tenant_id": "tenant-uuid",
"precedent_name": "Cigna IOP Frequency Limit Victory 2024",
"payer": "Cigna",
"denial_type": "Frequency Limit",
"loc": "IOP",
"status": "Complete",
"outcome": "Won",
"time_to_resolution_days": 14,
"evidence_score": "Strong",
"created_by": "user-uuid",
"created_at": "2024-04-14T10:30:00Z",
"updated_at": "2024-04-14T10:30:00Z",
"archived": false
}
Errors
400 Bad Request — Invalid request schema (missing required field, wrong type)
{
"error": "VALIDATION_ERROR",
"details": [
{ "path": "payer", "message": "Required" }
]
}
422 Unprocessable Entity — PHI detected in request
{
"status": "error",
"type": "phi_detected",
"message": "Protected Health Information detected: phone",
"offending_identifier": "phone"
}
403 Forbidden — User role insufficient
{
"error": "INSUFFICIENT_ROLE",
"message": "Create precedent requires admin or biller role"
}
GET /api/precedents
List precedent objects with pagination, filtering, and sorting.
Authentication: Required
Authorization: Any authenticated user
Query Parameters
| Parameter | Type | Default | Example |
| limit | integer | 20 | 50 |
| offset | integer | 0 | 100 |
| sort_by | string | created_at | outcome, payer, time_to_resolution_days |
| sort_direction | string | desc | asc |
| payer | string | (none) | Cigna |
| denial_type | string | (none) | Frequency Limit |
| loc | string | (none) | IOP |
| outcome | string | (none) | Won, Lost, Partial, Pending |
| cluster_id | string | (none) | Cigna \ | Frequency Limit \ | IOP |
| status | string | (none) | Complete, Draft, Pending |
| min_evidence_score | string | (none) | Minimal, Moderate, Strong |
| created_after | date | (none) | 2024-04-01 |
| created_before | date | (none) | 2024-12-31 |
| include_archived | boolean | false | true |
Example
GET /api/precedents?payer=Cigna&outcome=Won&limit=20&offset=0&sort_by=time_to_resolution_days&sort_direction=asc
Response (200 OK)
{
"data": [
{
"id": "prec-1",
"cluster_id": "Cigna | Frequency Limit |
IOP",
"precedent_name": "Cigna IOP Frequency Limit Victory 2024",
"payer": "Cigna",
"denial_type": "Frequency Limit",
"loc": "IOP",
"outcome": "Won",
"time_to_resolution_days": 14,
"evidence_score": "Strong",
"created_at": "2024-04-14T10:30:00Z"
}
],
"total": 247,
"limit": 20,
"offset": 0,
"has_more": true
}
GET /api/precedents/:id
Retrieve a single precedent object.
Authentication: Required
Authorization: Any authenticated user (tenant-filtered)
Response (200 OK)
{
"id": "prec-uuid",
"cluster_id": "Cigna | Frequency Limit |
IOP",
"tenant_id": "tenant-uuid",
"precedent_name": "Cigna IOP Frequency Limit Victory 2024",
"payer": "Cigna",
"denial_type": "Frequency Limit",
"loc": "IOP",
"status": "Complete",
"policy_reference": { ... },
"narrative": "...",
"denial_trigger": "...",
"evidence_kit": { ... },
"outcome": "Won",
"time_to_resolution_days": 14,
"evidence_score": "Strong",
"traceability_ledger": [ ... ],
"created_by": "user-uuid",
"created_at": "2024-04-14T10:30:00Z",
"updated_at": "2024-04-14T10:30:00Z",
"archived": false
}
Errors
404 Not Found — Precedent doesn't exist or is archived
{ "error": "NOT_FOUND" }
PUT /api/precedents/:id
Update a precedent object. Creates immutable version snapshot; never overwrites original.
Authentication: Required
Authorization: admin, biller roles only
PHI Validation: Request body scanned for PHI; rejected with 422 if found
Request Body
Any fields can be updated:
{
"narrative": "Updated clinical narrative with additional evidence...",
"evidence_score": "Strong",
"outcome": "Partial",
"traceability_ledger": [ ... ]
}
Response (200 OK)
Returns updated precedent (same structure as GET /api/precedents/:id).
Behavior
Version Snapshot — Current state is snapshot to precedent_version_history table before update
Update — precedent_objects row updated in place
Audit Logging — Event logged to precedent_audit_log with user_id, changed_fields
Cluster Recalculation — Pipeline re-runs cluster aggregation for this cluster_id
POST /api/precedents/:id/outcome
Record appeal outcome for a precedent (shorthand for PUT with outcome field).
Authentication: Required
Authorization: admin, biller roles only
Request Body
{
"outcome": "Won",
"time_to_resolution_days": 14,
"notes": "Appeal approved by medical reviewer"
}
Response (200 OK)
{
"id": "prec-uuid",
"outcome": "Won",
"time_to_resolution_days": 14,
"updated_at": "2024-04-14T10:30:00Z"
}
GET /api/precedents/:id/versions
Retrieve version history for a precedent.
Authentication: Required
Authorization: Any authenticated user
Response (200 OK)
{
"data": [
{
"version_number": 1,
"snapshot": { ... full precedent state ... },
"saved_by": "user-uuid",
"saved_at": "2024-04-14T10:00:00Z",
"version_reason": "Initial creation"
},
{
"version_number": 2,
"snapshot": { ... full precedent state after update ... },
"saved_by": "user-uuid",
"saved_at": "2024-04-14T10:30:00Z",
"version_reason": "Updated outcome to Won"
}
]
}
POST /api/precedents/:id/apply
Record that this precedent was used for an appeal case (increments reuse counter).
Authentication: Required
Authorization: admin, biller roles only
Request Body
{
"appeal_id": "appeal-uuid",
"notes": "Used as precedent for Cigna frequency limit appeal"
}
Response (200 OK)
{ "success": true }
DELETE /api/precedents/:id
Archive a precedent (soft delete).
Authentication: Required
Authorization: admin role only
Response (200 OK)
{
"success": true,
"id": "prec-uuid",
"archived": true,
"archived_at": "2024-04-14T10:30:00Z"
}
GET /api/precedents/:id/export
Export single precedent as JSON or CSV, with optional de-identification.
Authentication: Required
Authorization: admin, biller roles (biller cannot de-identify)
Query Parameters:
- format (json|csv) — default: json
- deidentify (true|false) — default: false. Admin only. Applies HIPAA Safe Harbor removal.
- omit_npi (true|false) — default: false. Admin only. Removes hashed NPI field.
Examples
# Export as JSON
GET /api/precedents/prec-uuid/export?format=json
Export de-identified (admin only)
GET /api/precedents/prec-uuid/export?format=json&deidentify=true
Export as CSV
GET /api/precedents/prec-uuid/export?format=csv
Export de-identified CSV without NPI (research publication)
GET /api/precedents/prec-uuid/export?format=csv&deidentify=true&omit_npi=true
Response (200 OK)
JSON:
{
"id": "prec-uuid",
"precedent_name": "[De-identified precedent]",
"payer": "Cigna",
"narrative": "[Patient Name] with GAF 35 appealed...",
...
}
CSV:
id,precedent_name,payer,denial_type,outcome,time_to_resolution_days
prec-uuid,[De-identified precedent],Cigna,Frequency Limit,Won,14
Errors
403 Forbidden — Biller attempting de-identified export
{
"error": "INSUFFICIENT_ROLE",
"message": "De-identified exports require admin role"
}
422 Unprocessable Entity — SUD data cannot be de-identified without consent
{
"error": "SUD_EXCLUSION",
"message": "42 CFR Part 2: SUD-origin records cannot be included in external exports"
}
POST /api/precedents/export
Bulk export multiple precedents as JSON or CSV with optional de-identification.
Authentication: Required
Authorization: admin role only
Request Body
Same as GET /api/precedents (query filters as POST body):
{
"payer": "Cigna",
"outcome": "Won",
"limit": 1000,
"offset": 0
}
Query Parameters
format (json|csv) — default: json
deidentify (true|false) — default: false
omit_npi (true|false) — default: false
Response (200 OK)
JSON with metadata:
{
"data": [ ... precedents array ... ],
"total": 347,
"exported_at": "2024-04-14T10:30:00Z",
"deidentified": true,
"deidentification_method": "HIPAA Safe Harbor §164.514(b)(2)",
"total_after_deidentify": 347,
"total_after_suppression": 340,
"suppressed_count": 7,
"sud_excluded_count": 0
}
See Also
OVERVIEW.md — Pagination, error handling
AUTHENTICATION.md — Auth requirements
ERROR-HANDLING.md — Error response formats
ENDPOINTS-MARKETPLACE.md — Cluster queries that reference precedents