API Endpoints v2
This API allows healthcare providers to create, update, and manage patient care plans. Each plan tracks clinical zones with recommended actions, maintains a full version history, supports optimistic concurrency control to prevent lost updates, and includes an activation, approval, and publication workflow.
Table of Contents
- Environment
- Activation
- Approval Lifecycle
- Publication Lifecycle
- How Versioning Works
- Endpoints
- Search Filters
- Error Response
- Implementation Notes
Environment
Development environment URL (no HelseID): https://planer.dev.nhn.no/
Swagger UI: https://planer.dev.nhn.no/api/swagger/
All requests and responses use Content-Type: application/json.
Activation
A plan has no status enum. Instead, activation is a pointer: active_version is the content version that can currently be shown to patients, or null if the plan has no active version.
- New plans start with no active version.
POST $activatesetsactive_versionto a specific content version.POST $deactivateclearsactive_version— the plan should no longer be shown to patients.PlanResponse.is_activeistrueonly when the returned content version is the active one.GET /api/v2/plan/{id}/activereturns the currently active version (404 if none).
Activation transitions and who made them are recorded in the version history via updated_by, updated_at, and comment.
Approval Lifecycle
Separate from activation, each plan tracks approval state per content version in the approvals array. Each entry has:
version— the content version the approval refers tostatus—requested,given, orrejectedupdated_at— when the state was last changed
Approval commands ($approval-requested, $approval-given, $approval-rejected) require both revision (for optimistic locking) and version (the content version being approved). A new content version starts with no approval entry; approving one version does not affect any other version.
Publication Lifecycle
Separate from activation and approval, each plan tracks per-provider publication state in the publication_status array. This allows different providers to independently publish or unpublish a version to their own patient-facing systems.
Each entry has:
provider_id— the provider that published or unpublishedversion— the content version that was publishedstatus—publishedorunpublishedupdated_at— when the state was last changed
When a provider publishes a plan, they specify which content version to publish via $mark-published / $mark-unpublished. The array contains one entry per provider that has published or unpublished the plan.
How Versioning Works
Each plan has two version counters:
Revision (revision):
- Starts at
1when the plan is created. - Incremented by the server on every mutation.
- Every mutation (except create) requires the current
revisionin the request body. If it doesn't match the server's revision, you'll get a409 Conflict. This prevents lost updates when multiple users edit the same plan simultaneously.
Content version (version):
- Starts at
1when the plan is created. - Incremented only when clinical content is updated via
$update-content. - Referenced by
$activate, the approval commands, and$mark-published/$mark-unpublishedto specify which content snapshot they apply to.
GET endpoints always return the latest content version. Old versions are immutable — once created, a version never changes. Retrieve any past version via GET /api/v2/plan/{id}/version/{version}, which returns the historical content with current approvals, publication_status, and active_version merged in.
Endpoints
For request/response schemas, parameters, and status codes, use the Swagger UI.
| Method | Path | Description |
|---|---|---|
POST |
/api/v2/plan |
Create a new plan (starts with no active version) |
GET |
/api/v2/plan/{id}/$latest |
Latest version from write-side event store |
GET |
/api/v2/plan/{id} |
Latest version from read-side projection |
GET |
/api/v2/plan/{id}/active |
Currently active version (404 if none) |
GET |
/api/v2/plan/{id}/version/{version} |
Specific historical version |
GET |
/api/v2/plan/{id}/history |
Version history (audit trail) |
GET |
/api/v2/plan?patient={nin} |
Search plans by patient |
POST |
/api/v2/plan/{id}/$update-content |
Update clinical content |
POST |
/api/v2/plan/{id}/$activate |
Set a content version as the active version |
POST |
/api/v2/plan/{id}/$deactivate |
Clear the active version |
POST |
/api/v2/plan/{id}/$approval-requested |
Request approval for a content version |
POST |
/api/v2/plan/{id}/$approval-given |
Grant approval for a content version |
POST |
/api/v2/plan/{id}/$approval-rejected |
Reject approval for a content version |
POST |
/api/v2/plan/{id}/$mark-published |
Mark a content version as published for the requesting provider |
POST |
/api/v2/plan/{id}/$mark-unpublished |
Mark a content version as unpublished for the requesting provider |
Search Filters
GET /api/v2/plan requires patient (patient NIN) and supports the following optional filters:
| Parameter | Values | Description |
|---|---|---|
patient |
NIN | Required. Only plans for this patient are returned |
is_active |
true / false |
Filter by whether the plan has an active version |
updated_since |
RFC3339 timestamp | Only plans updated at or after this timestamp |
published |
true / false |
Filter by publication state for the requesting provider. Requires the orgnr_parent header identifying the provider |
Error Response
All error responses use this structure:
{
"error": "plan not found",
"code": "NOT_FOUND",
"details": "extra context"
}
| Field | Type | Description |
|---|---|---|
error |
string |
Human-readable error message |
code |
string |
Machine-readable error code (optional) |
details |
any |
Optional extra context |
Implementation Notes
This API uses CQRS (Command Query Responsibility Segregation) and event sourcing under the hood. The CQRS separation is internal; externally all v2 endpoints are unified under /api/v2/. This provides scalability and consistency guarantees without exposing the CQRS pattern to API consumers.