HTTP API¶
The service exposes a versioned JSON API under /api/v1. The interactive OpenAPI documentation is available at /api/docs.
Resources¶
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/health |
Liveness and runtime version. |
GET |
/api/v1/jobs |
List bounded operational jobs. |
POST |
/api/v1/jobs/demo |
Create a provenance-marked synthetic demo job. |
GET |
/api/v1/jobs/{job_id} |
Retrieve one job and its review packet. |
POST |
/api/v1/jobs/{job_id}/review |
Record a confirmed or rejected human review decision. |
GET |
/api/v1/governance/model |
Read the durable model-governance record. |
PUT |
/api/v1/governance/model |
Record a designated operator’s approval boundary. |
POST |
/api/v1/core/receipts |
Submit one confirmed, correlation-bound Core receipt. |
POST |
/api/v1/match/medicine |
Rank one medication name against the index (read-only; optional doseText selects the strength variant). |
POST |
/api/v1/match/feedback |
Record one attributed accept/reject vote about a resolved match. |
POST |
/api/v1/index/import |
Upsert medication index entries (attributed) and reload the in-memory index. |
POST |
/api/v1/extract/medicines |
List every legible medication line on an uploaded image (base64) and match each against the index. Stateless: no job, no packet, no clinical record. |
Idempotent creation¶
POST /api/v1/jobs/demo requires an Idempotency-Key header. Repeating the same key returns the original job rather than creating another operational record.
curl -X POST "$OCR_OPS_URL/api/v1/jobs/demo" \
-H "Idempotency-Key: demo-intake-20260822-001"
Mutation attribution¶
Every mutating request carries an explicit actor envelope:
{
"actorId": "operator-123",
"actorName": "Designated operator"
}
Review and governance operations additionally require a non-empty reason. The system rejects empty, malformed, or state-incompatible requests with a problem response.
Matching and lab endpoints¶
/match/medicine is pure deterministic matching — no model call, no persistence — and returns the safety status (candidate / ambiguous / abstained), the resolved reference id when unambiguous, and ranked alternatives with scores. /extract/medicines accepts {"imageBase64", "mediaType"} (10 MiB limit enforced before decoding), runs multi-line Gemini extraction, matches every line, and returns per-line dose/frequency text plus a deterministic frequencyNormalized schedule. /index/import and /match/feedback carry the standard actor envelope; index changes reload the in-memory index immediately. The browser front end for these flows is the internal /lab page.
Problem responses¶
All API failures use application/problem+json. Each response has stable type, title, status, detail, and instance fields. Clients should branch on status and retain the type value for observability; they must not parse human-readable detail text.
{
"type": "https://nirog.example/problems/http-404",
"title": "Request could not be completed",
"status": 404,
"detail": "job was not found",
"instance": "/api/v1/jobs/example"
}
Core receipts¶
The Core receipt endpoint rejects a job unless it is confirmed. It requires a signed correlation assertion from Core and hashes the supplied review reason before sending the downstream request. It never forwards document text, candidate details, or non-diagnostic context.
Private Core dispatcher intake¶
The deployed runtime also receives a hidden server-to-server POST /internal/ocr/events contract. It accepts Core's existing evidence.ocr.requested.v1 event with profileId, evidenceId, ocrJobId, and correlationId. The caller must supply x-nirog-dispatcher-secret; unauthorized delivery receives a problem response.
The intake is idempotent by Core OCR job ID. A replay returns the existing OCR Ops operational job without a second Gemini extraction. The endpoint is excluded from browser-facing OpenAPI discovery and never accepts uploaded evidence bytes.