Skip to content

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.