# eUput — Serbian National Laboratory Services Registry API > The Serbian national laboratory-services registry (eUput) exposes a REST API for integrating hospital (BIS), laboratory (LIS) and referral systems. All responses are JSON. All data derives from the national nomenclature: NU services (NU-XXXX), NK components (NU-KO-XXXX), LOINC and RFZO L-code links, sampling types (TU-XXX) and per-institution local mappings. Human documentation (Serbian): /dev. Machine-readable OpenAPI 3.1: /api/openapi. This file is the complete specification — an AI coding agent needs only this file, the base URL, and an institution API key (issued privately at onboarding, never public). ## Authentication Live /api/registry/* calls accept TWO credential shapes on the x-api-key header. (1) Institution key (eup_ prefix): sent WITH x-institution-id (the institution's numeric id), issued by the registry administrator during onboarding — valid on all endpoints including local-code resolution (/resolve). (2) GENERAL system key (eur_ prefix): for BIS/ZIS and other external consumers, sent ALONE (no x-institution-id), valid on national read endpoints (service lists, institutions for a service, institution search, changelog, translate) — NOT valid on /resolve. Keys are NEVER public and must not ship in client-side (browser) code. POST /api/registry/elab/resolve uses the system-level x-elab-api-key header (national eLab system only). Exceptions to the key rule: GET /api/registry/export/manifest is fully PUBLIC (no key), and GET /api/registry/export is SCOPED — it works without a key (public content scope: clean NU/NK nomenclature with no internal identifiers), while an institution key or the eLab key automatically widens the content. A call with a WRONG key always returns 401 — sent credentials are never silently downgraded to a lower scope. ``` curl "https://BASE_URL/api/registry/services?q=glukoza" \ -H "x-api-key: YOUR_API_KEY" \ -H "x-institution-id: YOUR_INSTITUTION_ID" ``` ## Data model - **NU service (NU-XXXX)** — National laboratory service or panel — the ORDERING unit on a referral. Carries category, analyte, sampling type (TU), LOINC and RFZO L-code links. - **NK component (NU-KO-XXXX)** — National component (analyte) — the RESULT unit a LIS reports. A panel (NU) contains multiple components; components inherit sampling types from their panels. - **Sampling type (TU-XXX)** — National registry entry: specimen class (serum/plasma, urine, CSF...) + collection method (venipuncture, 24h collection...). - **Institution** — Healthcare institution with live/onboarding status. Two SEPARATE identifier sets that meet at the national service: the eLab set (elabCode = eLab record code, elabId = eLab service id) and the LIS set (lisCode = the TRUE local LIS code from the uploaded catalog with a confirmed national decision). - **Mapping** — A link from an institution's local code to an NU service (direct) or NK component (indirect). Indirect = the local code reports an analyte contained in some NU panel. ## Typical integration flows - **Referral (uput) creation**: search services (`GET /api/registry/services?q=...`) → pick an NU code → `GET /api/registry/services/{code}/institutions` for institutions that can perform it (works for NU and NK codes) → `GET /api/registry/institutions/nearest?lat=..&lng=..&service=NU-XXXX` for the closest one. - **LIS integration**: `GET /api/registry/resolve?lis=` translates a local code to the national record; `GET /api/registry/institutions/{code}/services/{concept}` answers "how does MY site perform NU-XXXX". - **Cache sync**: `GET /api/registry/changelog` returns deltas between published nomenclature releases. - **Code translation**: `GET /api/registry/translate?system=rfzo-l&code=L000414&target=loinc` (FHIR Parameters response). ## Endpoints ### GET /api/registry/services Main registry search. Search by whatever you have: NU code, Serbian/English name (diacritic-insensitive), RFZO L-code, LOINC code, local LIS code or eLab id. Also covers the indirect path: a local code mapped only to an NK component surfaces every panel containing it. Returns active services with LOINC/L-code links, sampling type and offering institutions. **Auth**: institution key (`x-api-key` eup_ + `x-institution-id`) OR a general BIS/ZIS system key (`x-api-key` eur_ alone, no institution header). | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `q` | query | string | no | Free text or any code (NU, L-code, LOINC, local, eLab id). | | `category` | query | string | no | Exact category (e.g. Biohemija). | | `institution` | query | string | no | Institution code — only services that institution offers. | | `hasLcode` | query | boolean | no | Filter by RFZO L-code coverage. | | `limit` | query | integer | no | Max results (default 25, max 100). | | `offset` | query | integer | no | Pagination offset. | Example request: ``` curl "https://BASE_URL/api/registry/services?q=glukoza&limit=5" \ -H "x-api-key: YOUR_API_KEY" -H "x-institution-id: YOUR_INSTITUTION_ID" ``` Example response: ```json { "total": 3, "returned": 1, "items": [{ "code": "NU-0022", "displaySr": "Glukoza", "category": "Biohemija", "unit": "mmol/L", "specimen": "Serum", "samplingType": { "code": "TU-001", "nameSr": "Venska krv — serum/plazma", "specimenClass": "Serum/plazma", "collectionMethod": "Venepunkcija" }, "loinc": [{ "code": "2345-7", "display": "Glucose [Moles/volume] in Serum or Plasma" }], "lcodes": [{ "code": "L000414", "display": "Glukoza u serumu" }], "institutions": [{ "code": "ELAB-881244", "name": "Dom zdravlja Ljubovija", "localCode": "280", "elabId": "1024" }] }] } ``` ### GET /api/registry/services/{code} Everything the registry knows about one NU service: full medical identity (analyte, specimen, method, scale), sampling type, definitions, revision metadata, all code mappings (LOINC, L-codes), panel components with local-mapping counts, and every institution offering the service. **Auth**: institution key (`x-api-key` eup_ + `x-institution-id`) OR a general BIS/ZIS system key (`x-api-key` eur_ alone, no institution header). | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `code` | path | string | yes | NU service code (e.g. NU-0022). | Example request: ``` curl "https://BASE_URL/api/registry/services/NU-0022" \ -H "x-api-key: YOUR_API_KEY" -H "x-institution-id: YOUR_INSTITUTION_ID" ``` Example response: ```json { "service": { "code": "NU-0022", "displaySr": "Glukoza", "identity": { "analyte": "Glukoza", "specimen": "Serum", "method": null, "scaleType": "Qn", "unitUcum": "mmol/L" }, "samplingType": { "code": "TU-001", "nameSr": "Venska krv — serum/plazma", "specimenClass": "Serum/plazma", "collectionMethod": "Venepunkcija" }, "meta": { "status": "active", "revision": 3 }, "loinc": [{ "code": "2345-7", "display": "..." }], "lcodes": [{ "code": "L000414", "display": "..." }], "components": [], "institutions": [{ "code": "ELAB-881244", "name": "Dom zdravlja Ljubovija", "city": "Ljubovija", "localCode": "280", "elabId": "1024" }] } } ``` ### GET /api/registry/services/{code}/institutions Purpose-built for REFERRAL CREATION: which live institutions can perform this? Works for BOTH national code kinds. For an NU service/panel it returns institutions with an active mapping to the service. For an NK component it returns institutions that deliver the component directly (standalone LIS mapping) OR indirectly via a containing panel — the via field distinguishes cases (direct | panel | both), and viaPanels lists which panels to order for indirect ones. Only live institutions, with coordinates for map display. **Auth**: institution key (`x-api-key` eup_ + `x-institution-id`) OR a general BIS/ZIS system key (`x-api-key` eur_ alone, no institution header). | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `code` | path | string | yes | NU code (NU-XXXX) or NK code (NU-KO-XXXX). | | `city` | query | string | no | City filter (case-insensitive exact match). | | `type` | query | string | no | Institution type (laboratory | hospital | clinic | private). | Example request: ``` curl "https://BASE_URL/api/registry/services/NU-KO-0039/institutions?city=Beograd" \ -H "x-api-key: YOUR_API_KEY" -H "x-institution-id: YOUR_INSTITUTION_ID" ``` Example response: ```json { "kind": "component", "component": { "code": "NU-KO-0039", "name": "Kalcijum" }, "returned": 24, "institutions": [{ "code": "ELAB-881244", "name": "Dom zdravlja Ljubovija", "city": "Ljubovija", "type": "clinic", "latitude": 44.23, "longitude": 19.08, "via": "both", "localCode": "K-155", "viaPanels": [{ "code": "NU-0369", "name": "Krvna slika (trodelna)", "elabCode": "955" }] }] } ``` > For NU codes the response has kind="service" and each institution carries elabCode/elabId instead of via/viaPanels. ### GET /api/registry/institutions General institution search for referral UIs: by name/code/city (diacritic-insensitive), type, and whether they actively offer a given NU service. Only live institutions, each with coordinates and mappingCount as a size signal. **Auth**: institution key (`x-api-key` eup_ + `x-institution-id`) OR a general BIS/ZIS system key (`x-api-key` eur_ alone, no institution header). | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `q` | query | string | no | Free text over code, name, city. | | `city` | query | string | no | Exact city. | | `type` | query | string | no | Institution type. | | `service` | query | string | no | NU code — only institutions offering it. | | `limit` | query | integer | no | Max results (default 25, max 100). | | `offset` | query | integer | no | Pagination offset. | Example request: ``` curl "https://BASE_URL/api/registry/institutions?q=ljubovija" \ -H "x-api-key: YOUR_API_KEY" -H "x-institution-id: YOUR_INSTITUTION_ID" ``` Example response: ```json { "total": 1, "returned": 1, "institutions": [{ "code": "ELAB-881244", "name": "Dom zdravlja Ljubovija", "city": "Ljubovija", "type": "clinic", "latitude": 44.23, "longitude": 19.08, "mappingCount": 412 }] } ``` ### GET /api/registry/institutions/nearest For referral systems: live institutions ordered by distance from a point (Haversine great-circle), optionally filtered by radius, type and an NU service they must offer. **Auth**: institution key (`x-api-key` eup_ + `x-institution-id`) OR a general BIS/ZIS system key (`x-api-key` eur_ alone, no institution header). | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `lat` | query | number | yes | Latitude (-90..90). | | `lng` | query | number | yes | Longitude (-180..180). | | `limit` | query | integer | no | Max results (default 10, max 50). | | `maxKm` | query | number | no | Radius filter in kilometers. | | `type` | query | string | no | Institution type. | | `service` | query | string | no | NU code — only institutions offering it. | Example request: ``` curl "https://BASE_URL/api/registry/institutions/nearest?lat=44.8&lng=20.46&limit=5" \ -H "x-api-key: YOUR_API_KEY" -H "x-institution-id: YOUR_INSTITUTION_ID" ``` Example response: ```json { "returned": 2, "institutions": [{ "code": "ELAB-881244", "name": "Dom zdravlja Ljubovija", "city": "Ljubovija", "distanceKm": 3.42, "latitude": 44.23, "longitude": 19.08 }] } ``` ### GET /api/registry/institutions/{code}/services/{concept} Execution API for LIS systems: "what is NU-XXXX at my site?" Returns the institution's eLab catalog items mapped to that national service and, separately, lisCodes — the LIS šifarnik entries with a CONFIRMED decision on the same NU (item.lisCode = the first of them). Multiple items = the institution distinguishes method variants; primary marks the default. Method choice happens HERE, at the execution site — never on the referral. **Auth**: institution key (`x-api-key` eup_ + `x-institution-id`) OR a general BIS/ZIS system key (`x-api-key` eur_ alone, no institution header). | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `code` | path | string | yes | Institution code or numeric eLab id. | | `concept` | path | string | yes | NU service code. | Example request: ``` curl "https://BASE_URL/api/registry/institutions/ELAB-881244/services/NU-0022" \ -H "x-api-key: YOUR_API_KEY" -H "x-institution-id: YOUR_INSTITUTION_ID" ``` Example response: ```json { "institution": { "code": "ELAB-881244", "name": "Dom zdravlja Ljubovija" }, "service": { "code": "NU-0022", "name": "Glukoza" }, "returned": 1, "items": [{ "elabCode": "280", "elabId": "1024", "elabDisplay": "Glukoza u serumu", "lisCode": "GLU-S", "lisDisplay": "Glukoza (serum)", "vendor": null, "method": null, "primary": true }], "lisCodes": [{ "lisCode": "GLU-S", "lisDisplay": "Glukoza (serum)" }] } ``` > Fields localCode/localDisplay are DEPRECATED aliases of elabCode/elabDisplay — migrate to elabCode; LIS codes live in lisCodes. ### GET /api/registry/resolve The calling institution submits a code and gets the fully linked national record. Three forms: ?code= (eLab record code), ?elab= (eLab service id), ?lis= (TRUE local LIS code from the uploaded catalog). The institution comes from auth headers — it can only resolve its own catalog. The match field discriminates: service (directly mapped to an NU), components (mapped only to NK analytes — lists panels containing them), none (no mapping). **Auth**: institution key only — `x-api-key` (eup_) + `x-institution-id` headers. | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `code` | query | string | no | eLab record code (default form). | | `elab` | query | string | no | eLab service id. | | `lis` | query | string | no | TRUE local LIS code. | Example request: ``` curl "https://BASE_URL/api/registry/resolve?code=280" \ -H "x-api-key: YOUR_API_KEY" -H "x-institution-id: YOUR_INSTITUTION_ID" ``` Example response: ```json { "match": "service", "by": "elabCode", "service": { "code": "NU-0022", "displaySr": "Glukoza", "loinc": [], "lcodes": [], "components": [] } } ``` ### GET /api/registry/translate FHIR ConceptMap $translate-style: translates a code from a source system to all matching codes in the target system via the national concept hub. Systems: rfzo-l, loinc, concept (an NU code directly). Response is a FHIR Parameters resource. **Auth**: institution key (`x-api-key` eup_ + `x-institution-id`) OR a general BIS/ZIS system key (`x-api-key` eur_ alone, no institution header). | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `system` | query | string | yes | Source system: rfzo-l | loinc | concept. | | `code` | query | string | yes | Code in the source system. | | `target` | query | string | yes | Target system: rfzo-l | loinc | concept. | Example request: ``` curl "https://BASE_URL/api/registry/translate?system=rfzo-l&code=L000414&target=loinc" \ -H "x-api-key: YOUR_API_KEY" -H "x-institution-id: YOUR_INSTITUTION_ID" ``` Example response: ```json { "resourceType": "Parameters", "parameter": [ { "name": "result", "valueBoolean": true }, { "name": "match", "part": [{ "name": "concept", "valueCoding": { "system": "loinc", "code": "2345-7", "display": "Glucose [Moles/volume] in Serum or Plasma" } }] } ] } ``` ### GET /api/registry/changelog What changed between the previous published release and the requested one — supports delta-syncing client caches. Without a parameter, returns the latest published release. **Auth**: institution key (`x-api-key` eup_ + `x-institution-id`) OR a general BIS/ZIS system key (`x-api-key` eur_ alone, no institution header). | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `version` | query | string | no | Release version (e.g. 2026.1). Default: latest. | Example request: ``` curl "https://BASE_URL/api/registry/changelog" \ -H "x-api-key: YOUR_API_KEY" -H "x-institution-id: YOUR_INSTITUTION_ID" ``` Example response: ```json { "version": "2026.1", "publishedAt": "2026-07-01T10:00:00Z", "previousVersion": "2025.2", "changes": { "added": [], "changed": [], "retired": [] } } ``` ### POST /api/registry/elab/resolve National eLab system only (system-level x-elab-api-key, not an institution key). eLab submits its (id + description) pairs for ONE institution and gets the NU/NK mapping for each — including statuses for unmapped ones: pending_mapping (queued for AI analysis + review), ai_suggested (AI suggestion awaiting approval — NOT yet usable), not_applicable (whitelisted, not mappable), rejected, needs_description. **Auth**: system-level `x-elab-api-key` header (national eLab system only — NOT institution keys). | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `institutionId` | body | number | string | yes | Institution id or code (alternative: institutionCode). | | `items` | body | array | yes | Array of items: { kind: "service" | "analysis", id, description?, specimen? }. | Example request: ``` curl -X POST "https://BASE_URL/api/registry/elab/resolve" \ -H "x-elab-api-key: YOUR_ELAB_SYSTEM_KEY" -H "content-type: application/json" \ -d '{"institutionId":173,"items":[{"kind":"service","id":"2937","description":"25(OH) vitamin D"}]}' ``` Example response: ```json { "institution": { "id": 173, "code": "ELAB-881244" }, "results": [ { "kind": "service", "id": "2937", "status": "mapped", "target": { "code": "NU-0509", "name": "25(OH) vitamin D" } }, { "kind": "analysis", "id": "643", "status": "pending_mapping", "requestId": 88 } ] } ``` ### GET /api/registry/export/manifest PUBLIC, no key. Small response with version, publish date and filenames of the latest published release. Recommended daily flow for offline integrations: call the manifest once a day, compare publishedAt with your last imported edition — if newer, download /api/registry/export. Filenames embed the publish date, so a new edition is detectable from the name alone. **Auth**: none — public endpoint, no key required. Example request: ``` curl "https://BASE_URL/api/registry/export/manifest" ``` Example response: ```json { "version": "2026.1", "publishedAt": "2026-07-29T15:38:12.087Z", "publishDate": "2026-07-29", "conceptCount": 677, "exportUrl": "/api/registry/export", "formats": ["json", "xml", "csv"], "filenames": { "public": { "json": "euput-registar-2026.1-2026-07-29-javni.json" }, "elab": { "json": "euput-registar-2026.1-2026-07-29-elab.json" }, "lab": { "json": "euput-registar-2026.1-2026-07-29-lab-{USTANOVA}.json" } } } ``` ### GET /api/registry/export The complete nomenclature of the latest published release in one file — for institutions preferring daily offline imports over the live API. Content scope is decided AUTOMATICALLY from credentials: no key → public scope (clean NU/NK nomenclature with LOINC/L-codes/sampling types, no internal IDs); x-api-key + x-institution-id → lab scope (+ that institution's OWN local LIS mappings only); x-elab-api-key → elab scope (+ all institutions' eLab mappings). A wrong key returns 401 — never silently downgraded. The Content-Disposition filename embeds version and publish date. **Auth**: optional — no key gives the public scope; `x-api-key` + `x-institution-id` gives the institution (lab) scope; `x-elab-api-key` gives the eLab scope. A wrong key returns 401 (never silently downgraded). | Parameter | In | Type | Required | Description | |---|---|---|---|---| | `format` | query | string | no | json (default), xml or csv (UTF-8 BOM, ";" separator). | | `version` | query | string | no | A specific published release (default: latest). | Example request: ``` # javni nivo (bez ključa) curl -o registar.json "https://BASE_URL/api/registry/export?format=json" # nivo ustanove curl -H "x-api-key: YOUR_API_KEY" -H "x-institution-id: YOUR_INSTITUTION_ID" \ -o registar-lab.json "https://BASE_URL/api/registry/export?format=json" ``` Example response: ```json { "resourceType": "CodeSystem", "version": "2026.1", "date": "2026-07-29T15:38:12.087Z", "scope": { "kind": "public" }, "count": 677, "concept": [{ "code": "NU-0022", "display": "Glukoza", "loinc": ["2345-7"], "rfzoLCodes": ["L000414"], "property": [{ "code": "samplingType", "valueString": "TU-001" }] }], "component": [{ "code": "NU-KO-0039", "display": "Kalcijum", "loinc": "17861-6" }] } ``` > The response carries X-Registry-Version and X-Registry-Published-At headers so clients can record the import without parsing the body. > Filename: euput-registar-{version}-{YYYY-MM-DD}-{javni|elab|lab-INSTITUTION}.{json|xml|csv}. > Same content per version: /api/releases/{version}/export (same access scopes). ## Error handling - `401` — missing or invalid `x-api-key` / `x-institution-id` headers. - `404` — unknown code / institution / release (body carries an `error` field or a FHIR OperationOutcome). - `400` — missing or invalid required parameters (body explains which). - All errors are JSON.