{"openapi":"3.1.0","info":{"title":"eUput — Serbian National Laboratory Services Registry API","version":"1.0.0","description":"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.\n\nLive /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."},"servers":[{"url":"https://BASE_URL","description":"Replace with the registry deployment URL"}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"Institution API key issued at onboarding (private)."},"institutionId":{"type":"apiKey","in":"header","name":"x-institution-id","description":"The institution's numeric id."},"elabApiKey":{"type":"apiKey","in":"header","name":"x-elab-api-key","description":"System-level key for the national eLab system only."},"generalApiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"General system key (eur_ prefix) for BIS/ZIS consumers — sent alone, WITHOUT x-institution-id. Valid on national read endpoints only (not /resolve)."}}},"paths":{"/api/registry/services":{"get":{"operationId":"services-search","summary":"Search national services","description":"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.","security":[{"apiKey":[],"institutionId":[]},{"generalApiKey":[]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Free text or any code (NU, L-code, LOINC, local, eLab id).","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"Exact category (e.g. Biohemija).","schema":{"type":"string"}},{"name":"institution","in":"query","required":false,"description":"Institution code — only services that institution offers.","schema":{"type":"string"}},{"name":"hasLcode","in":"query","required":false,"description":"Filter by RFZO L-code coverage.","schema":{"type":"boolean"}},{"name":"limit","in":"query","required":false,"description":"Max results (default 25, max 100).","schema":{"type":"integer"}},{"name":"offset","in":"query","required":false,"description":"Pagination offset.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"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"}]}]}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}},"/api/registry/services/{code}":{"get":{"operationId":"service-detail","summary":"Full service record","description":"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.","security":[{"apiKey":[],"institutionId":[]},{"generalApiKey":[]}],"parameters":[{"name":"code","in":"path","required":true,"description":"NU service code (e.g. NU-0022).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"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"}]}}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}},"/api/registry/services/{code}/institutions":{"get":{"operationId":"service-institutions","summary":"Institutions performing a service or component (referral)","description":"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.\n\nFor NU codes the response has kind=\"service\" and each institution carries elabCode/elabId instead of via/viaPanels.","security":[{"apiKey":[],"institutionId":[]},{"generalApiKey":[]}],"parameters":[{"name":"code","in":"path","required":true,"description":"NU code (NU-XXXX) or NK code (NU-KO-XXXX).","schema":{"type":"string"}},{"name":"city","in":"query","required":false,"description":"City filter (case-insensitive exact match).","schema":{"type":"string"}},{"name":"type","in":"query","required":false,"description":"Institution type (laboratory | hospital | clinic | private).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"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"}]}]}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}},"/api/registry/institutions":{"get":{"operationId":"institutions-search","summary":"Search institutions","description":"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.","security":[{"apiKey":[],"institutionId":[]},{"generalApiKey":[]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Free text over code, name, city.","schema":{"type":"string"}},{"name":"city","in":"query","required":false,"description":"Exact city.","schema":{"type":"string"}},{"name":"type","in":"query","required":false,"description":"Institution type.","schema":{"type":"string"}},{"name":"service","in":"query","required":false,"description":"NU code — only institutions offering it.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Max results (default 25, max 100).","schema":{"type":"integer"}},{"name":"offset","in":"query","required":false,"description":"Pagination offset.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"total":1,"returned":1,"institutions":[{"code":"ELAB-881244","name":"Dom zdravlja Ljubovija","city":"Ljubovija","type":"clinic","latitude":44.23,"longitude":19.08,"mappingCount":412}]}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}},"/api/registry/institutions/nearest":{"get":{"operationId":"institutions-nearest","summary":"Nearest institutions (geo)","description":"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.","security":[{"apiKey":[],"institutionId":[]},{"generalApiKey":[]}],"parameters":[{"name":"lat","in":"query","required":true,"description":"Latitude (-90..90).","schema":{"type":"number"}},{"name":"lng","in":"query","required":true,"description":"Longitude (-180..180).","schema":{"type":"number"}},{"name":"limit","in":"query","required":false,"description":"Max results (default 10, max 50).","schema":{"type":"integer"}},{"name":"maxKm","in":"query","required":false,"description":"Radius filter in kilometers.","schema":{"type":"number"}},{"name":"type","in":"query","required":false,"description":"Institution type.","schema":{"type":"string"}},{"name":"service","in":"query","required":false,"description":"NU code — only institutions offering it.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"returned":2,"institutions":[{"code":"ELAB-881244","name":"Dom zdravlja Ljubovija","city":"Ljubovija","distanceKm":3.42,"latitude":44.23,"longitude":19.08}]}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}},"/api/registry/institutions/{code}/services/{concept}":{"get":{"operationId":"institution-service","summary":"How an institution performs a service (execution API)","description":"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.\n\nFields localCode/localDisplay are DEPRECATED aliases of elabCode/elabDisplay — migrate to elabCode; LIS codes live in lisCodes.","security":[{"apiKey":[],"institutionId":[]},{"generalApiKey":[]}],"parameters":[{"name":"code","in":"path","required":true,"description":"Institution code or numeric eLab id.","schema":{"type":"string"}},{"name":"concept","in":"path","required":true,"description":"NU service code.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"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)"}]}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}},"/api/registry/resolve":{"get":{"operationId":"resolve","summary":"Reverse lookup of a local code (BIS/LIS)","description":"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).","security":[{"apiKey":[],"institutionId":[]}],"parameters":[{"name":"code","in":"query","required":false,"description":"eLab record code (default form).","schema":{"type":"string"}},{"name":"elab","in":"query","required":false,"description":"eLab service id.","schema":{"type":"string"}},{"name":"lis","in":"query","required":false,"description":"TRUE local LIS code.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"match":"service","by":"elabCode","service":{"code":"NU-0022","displaySr":"Glukoza","loinc":[],"lcodes":[],"components":[]}}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}},"/api/registry/translate":{"get":{"operationId":"translate","summary":"Code translation between systems (FHIR $translate)","description":"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.","security":[{"apiKey":[],"institutionId":[]},{"generalApiKey":[]}],"parameters":[{"name":"system","in":"query","required":true,"description":"Source system: rfzo-l | loinc | concept.","schema":{"type":"string"}},{"name":"code","in":"query","required":true,"description":"Code in the source system.","schema":{"type":"string"}},{"name":"target","in":"query","required":true,"description":"Target system: rfzo-l | loinc | concept.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"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"}}]}]}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}},"/api/registry/changelog":{"get":{"operationId":"changelog","summary":"Release changelog (delta sync)","description":"What changed between the previous published release and the requested one — supports delta-syncing client caches. Without a parameter, returns the latest published release.","security":[{"apiKey":[],"institutionId":[]},{"generalApiKey":[]}],"parameters":[{"name":"version","in":"query","required":false,"description":"Release version (e.g. 2026.1). Default: latest.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"version":"2026.1","publishedAt":"2026-07-01T10:00:00Z","previousVersion":"2025.2","changes":{"added":[],"changed":[],"retired":[]}}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}},"/api/registry/elab/resolve":{"post":{"operationId":"elab-resolve","summary":"eLab batch resolve (system-level)","description":"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.","security":[{"elabApiKey":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"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}]}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId","items"],"properties":{"institutionId":{"description":"Institution id or code (alternative: institutionCode)."},"items":{"description":"Array of items: { kind: \"service\" | \"analysis\", id, description?, specimen? }."}}}}}}}},"/api/registry/export/manifest":{"get":{"operationId":"export-manifest","summary":"Release manifest (daily check)","description":"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.","security":[],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"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"}}}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}},"/api/registry/export":{"get":{"operationId":"bulk-export","summary":"Bulk release export (offline import)","description":"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.\n\nThe response carries X-Registry-Version and X-Registry-Published-At headers so clients can record the import without parsing the body.\nFilename: euput-registar-{version}-{YYYY-MM-DD}-{javni|elab|lab-INSTITUTION}.{json|xml|csv}.\nSame content per version: /api/releases/{version}/export (same access scopes).","security":[{},{"apiKey":[],"institutionId":[]},{"elabApiKey":[]}],"parameters":[{"name":"format","in":"query","required":false,"description":"json (default), xml or csv (UTF-8 BOM, \";\" separator).","schema":{"type":"string"}},{"name":"version","in":"query","required":false,"description":"A specific published release (default: latest).","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"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"}]}}}},"401":{"description":"Missing or invalid API key headers"},"404":{"description":"Unknown code, institution or release"}}}}}}