Developer Documentation
Documentation for API consumers integrating with the Nompd API.
Contents
- Getting Started — Quickstart guide with curl examples to get up and running
- API Reference — Complete endpoint documentation with parameters, responses, and examples (v1 API-key and v2 HelseID endpoints)
- Authentication — How to authenticate using API keys (v1) or HelseID with DPoP (v2) and expected error responses
- Versioning and Diff — How to use version-based differential sync to fetch only changes
- Data Models — Full schema documentation for TreatmentGroup, ReimbursementGroup, and related types
- Changelog — Release notes and version history
- Working with Agents — How to connect AI agents such as Opencode to GitLab Milliways and Azure DevOps via MCP
- Code Flow Through Environments — GitLab Flow branch strategy for
main,test, andprod: feature promotion, patch/hotfix sync-back, and commit conventions
Changelog
All notable changes to the Nompd API will be documented in this file.
The format is based on Keep a Changelog.
[Unreleased]
Breaking Changes
New product structure — The
productsentries in bothtreatment-groupandreimbursement-groupresponses (v1 and v2) have a new shape. Thereference,type, anddisplayfields are removed, and the singleidentifierobject is replaced by anidentifierslist:{ "identifiers": [ { "system": "FEST/LegemiddelPakning/Varenummer", "value": "448992" }, { "system": "FEST/LegemiddelPakning/Id", "value": "ADDED_WHEN_FEST_FILE_IS_READ" }, { "system": "FEST/LegemiddelMerkevare/Id", "value": "ADDED_WHEN_FEST_FILE_IS_READ" }, { "system": "FEST/LegemiddelVirkestoff/Id", "value": "ADDED_WHEN_FEST_FILE_IS_READ" } ] }The identifier system is renamed from
FEST/VarenummertoFEST/LegemiddelPakning/Varenummer(the previoustypevalue is folded into the system string). TheFEST/*/Ididentifiers carry the placeholder valueADDED_WHEN_FEST_FILE_IS_READuntil FEST file ingestion populates the real values.productsare empty between the deployment of this change and the next sync.Renamed
itemstoentriesin the response envelope — The list endpoints (treatment-groupandreimbursement-group, v1 and v2) now return{ "currentVersion": ..., "entries": [...] }instead of{ "currentVersion": ..., "items": [...] }. The entry shape (data,version,lastChanged,isDeleted) is unchanged. No backwards-compatible parallel field — consumers must migrate.
Added
HelseID (DPoP) v2 endpoints — The data endpoints are now also served under
/api/v2/, authenticated with a HelseID access token using DPoP (RFC 9449) instead of theX-API-KEYheader:GET /api/v2/treatment-group,GET /api/v2/treatment-group/{id}GET /api/v2/reimbursement-group,GET /api/v2/reimbursement-group/{id}- Same data, query parameters, and response shapes as the v1 endpoints.
- Requests send
Authorization: DPoP <access-token>plus a per-requestDPoPproof header. The access token must be issued by HelseID with audiencenhn:nompdand thenhn:nompd/apiscope.Bearertokens are not accepted on v2 endpoints (they return401). - New status code:
403 Forbiddenwhen authenticated via HelseID but the access token is missing thenhn:nompd/apiscope. - v2 endpoints are not rate limited.
- Non-breaking — v1 endpoints and API key authentication are unchanged.
Rate limiting — The data endpoints (
/api/v1/treatment-group,/api/v1/reimbursement-group,/api/internal/sync) are now rate limited to 120 requests per minute per API key (sliding window, configurable viaRateLimiting__*environment variables). Exceeding the limit returns429 Too Many Requestswith aRetry-Afterheader (set to the rate-limit window length) and body{ "error": "Rate limit exceeded. Try again later." }. Requests without a valid API key (missing or unknown) share ananonymousbucket, keeping the number of limiter partitions bounded. Limiting can be disabled withRateLimiting__Enabled=false. The health endpoint and Swagger UI are not rate limited. Not a breaking change — only new 429 responses under load.Global exception handling — Unhandled exceptions now return
500 Internal Server Errorwith a JSON body containing acorrelationId. In the Development environment, the response also includesmessageandstackTrace. The correlation ID is reused from an incomingX-Correlation-Idheader when present, otherwise generated, and is echoed in theX-Correlation-Idresponse header. Exceptions are logged with their correlation ID.X-Correlation-Idon all responses — Every response (not only500errors) now includes anX-Correlation-Idresponse header, so any request can be traced in the server logs. A client-providedX-Correlation-Idrequest header is still reused when present. Non-breaking — only adds a response header.
Removed
- MongoDB deployment and helper scripts — Removed the MongoDB Helm chart values (
manifests/apps/mongodb), themongodbentries from the ArgoCD app-of-apps values, and the helper scriptscreate-mongodb-secret.sh,forward-db.sh, andrun-exporer.sh. PostgreSQL is the sole datastore. No API behavior or data contract changes.
Fixed
- Access token
typvalidation on v2 (HelseID) endpoints — Access tokens whosetypheader is notat+jwtare now rejected with401 Unauthorized, as required by the HelseID security profile. Previously thetypheader was not validated, so such tokens were accepted and the request was processed. - DPoP
htuvalidation behind TLS-terminating gateway — The forwarded-headers middleware now forwardsX-Forwarded-Protoand trusts only the source networks configured viaFORWARDED_TRUSTED_NETWORKS(comma-separated CIDR list) instead of all proxies. This ensuresrequest.Schemeishttpsfor DPoPhtucomparison while preventing header spoofing from untrusted sources. - Swallowed unhandled exceptions — The request logging middleware previously swallowed unhandled exceptions, which resulted in empty
200 OKresponses with no error log. Exceptions now propagate to the exception handler. - Client aborts — A client aborting a request no longer produces a
500response or an error log.
Breaking Changes
- Removed
/filtersub-route — Filter parameters are now query params on the main endpoint.GET /api/v1/treatment-group/filter?...→GET /api/v1/treatment-group?...GET /api/v1/reimbursement-group/filter?...→GET /api/v1/reimbursement-group?...- No backwards-compatible parallel endpoint — consumers must migrate.
[4.0.0] - 2026-06-17
Breaking Changes
- Renamed
medicinestoproductson bothTreatmentAlternativeandReimbursementGroup, and renamed the referenced typeMedicinalReferencetoProductReference.- JSON field:
medicines→products(affectsGET /api/v1/treatmentGroup,GET /api/v1/reimbursementGroup,GET /api/v1/reimbursementGroup/{id}, andGET /api/v1/reimbursementGroup/filter) - The element shape (
reference,type,identifier,display) is unchanged — only the array field name and the C# type name changed. - No backwards-compatible parallel field — consumers must migrate.
- JSON field:
[3.1.0] - 2026-05-07
Added
medicinesonReimbursementGroup— exposes the medicines covered by a reimbursement authorization. Sourced fromRegulatedAuthorization.ArticleNumber; emitted as aMedicinalReference[](FEST/Varenummer). Empty list when upstream provides no article number. Non-breaking additive change.
[3.0.0] - 2026-04-30
Breaking Changes
- Renamed
Behandlingsgruppemodel toTreatmentGroup— endpoint, model, and database table all renamed for consistency withReimbursementGroup.- Endpoint:
GET /api/v1/behandlingsgrupper→GET /api/v1/treatmentGroup - Table:
Behandlingsgrupper→TreatmentGroups - Model shape unchanged (same fields:
id,name,description,diseaseGroup,jurisdictions,validityPeriod,reviewDate,indications,treatmentAlternatives) - No backwards-compatible parallel endpoint — consumers must migrate.
- Endpoint:
Added
- Per-document version metadata in response wrapper —
VersionedItem<T>now exposesversion(the sync version when this item last changed) andlastChanged(ISO-8601 timestamp). Applies to bothtreatmentGroupandreimbursementGroupendpoints.
[2.0.0] - 2026-04-28
Breaking Changes
Replaced
Reimbursementmodel withReimbursementGroup— the endpoint, model shape, and database table have all been renamed and restructured.- Endpoint:
GET /api/v1/reimbursements→GET /api/v1/reimbursementGroup - Table:
Reimbursements→ReimbursementGroups - Model is reduced to three fields:
id,legalBasis(CodeableConcept),indications(CodeableConcept[]) - The previous
medicinalReferencesarray is removed pending a redesign legalBasisnow correctly carries the hjemmel (e.g., H-resept §950) sourced from the upstreamreimbursementRegulationextension, rather than the first coding fromBasisindicationscarries the diagnoses (e.g., ICD-10 codes) sourced fromBasis.Coding, with one CodeableConcept per coding
Consumers using the old endpoint must migrate. There is no backwards-compatible parallel endpoint.
- Endpoint:
[1.1.0] - 2026-04-28
Added
Raw FHIR persistence — Upstream FHIR resources are now stored in Postgres alongside the converted output, sharing the same version number per sync.
- New versioned collections:
PlanDefinitions,ActivityDefinitions,RegulatedAuthorizations - Each wrapped in
VersionedDocument<T>with the same upsert + soft-delete semantics as the output collections - Enables re-conversion if mapping logic changes, debugging via raw-vs-converted comparison, and full audit traceability
- New versioned collections:
Sync history — Every sync run writes one entry to the new
SyncHistorycollection with:Version,SyncedAt,DurationMs- Per-collection statistics (
AddedCount,UpdatedCount,UnchangedCount,DeletedCount) - Business keys of every added, updated, and deleted record (
AddedKeys,UpdatedKeys,DeletedKeys)
unchangedfield inSyncResult— ThePOST /api/internal/syncresponse now includes a count of records that were detected as unchanged.
Changed
Content-hash-based change detection —
VersionedDocument<T>has a newContentHashfield (SHA256 of BSON). Records only receive a newVersionif their hash actually changed (or their deletion state flipped). Previously every sync bumped the version on every record, which made?sinceVersion=Nreturn everything after each sync. Now diff queries return only the records that genuinely changed.- Migration impact: The first sync after deploy bumps the version on all existing records once (because they have no hash to compare against), then subsequent syncs only touch genuinely changed records.
Sync log line — Now includes
unchangedcount in the structured log message.
Internal
PlanDefinitionno longer has a[BsonId]attribute — it is now embedded insideVersionedDocument<PlanDefinition>, and the wrapper holds the database primary key.UpsertCollection<T>inSyncServicenow returnsSyncCollectionStats(counts plus business keys) instead of a counts-only tuple.
[1.0.0] - 2026-04-13
Added
Behandlingsgrupper endpoint (
GET /api/v1/behandlingsgrupper)- Returns treatment groups with ranked treatment alternatives
- Includes jurisdictions, indications, validity periods, and medicine references
Reimbursements endpoint (
GET /api/v1/reimbursements)- Returns reimbursement authorizations with regulation basis and indication codes
- Includes medicine references (FEST and NOMPD)
Version-based diff support
- All endpoints accept
?since-version=Nto return only changes since a given version - Responses include
currentVersionfor clients to track their sync state - Deleted items are included in diff responses with
isDeleted: true
- All endpoints accept
API key authentication
- All endpoints require
X-API-KEYheader - Keys are configured server-side and issued manually
- All endpoints require
Sync endpoint (
POST /api/internal/sync)- Triggers a full data sync from upstream FHIR APIs
- Intended to be called by a Kubernetes CronJob
- Returns sync statistics (added, updated, deleted, duration)