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 approval workflow.
Table of Contents
- Environment
- Data Model
- Status Lifecycle
- Approval Lifecycle
- How Versioning Works
- Endpoints
- Error Response
- Implementation Notes
Environment
Development environment URL (no HelseID): https://planer.dev.nhn.no/
Swagger UI: https://planer.dev.nhn.no/swagger/
All requests and responses use Content-Type: application/json.
Data Model
Create Request
| Field | Type | Description |
|---|---|---|
status |
string |
Initial status: draft, active, or revoked |
comment |
string |
Reason or note for the change |
subject |
object |
Patient the plan belongs to (read-only after create) |
clinical_content |
object |
Clinical content |
Update Content Request
| Field | Type | Description |
|---|---|---|
version |
int |
Required, must match current server version |
status |
string |
Current plan status |
comment |
string |
Reason or note for the change |
clinical_content |
object |
Updated clinical content |
Status Change Request
| Field | Type | Description |
|---|---|---|
version |
int |
Required, must match current server version |
comment |
string |
Reason for the status change |
Approval Request
| Field | Type | Description |
|---|---|---|
version |
int |
Required, must match current server version |
comment |
string |
Reason or note for the action |
Mutation Response
All mutation endpoints (POST) return:
| Field | Type | Description |
|---|---|---|
id |
string |
UUID of the plan |
version |
int |
New version number after the change |
To retrieve full plan data, use a GET request.
Response
| Field | Type | Description |
|---|---|---|
id |
string |
UUID, assigned by server on create (read-only) |
version |
int |
Current version number |
status |
string |
Current status |
updated_at |
string |
ISO 8601 timestamp, set by server (read-only) |
updated_by |
object |
Health personnel who last updated the plan |
subject |
object |
Patient the plan belongs to |
clinical_content |
object |
Clinical content |
approval_status |
string |
Approval state: approval_requested, approval_given, approval_rejected, or empty |
rejection_reason |
string |
Reason provided when approval was rejected |
revoke_reason |
string |
Reason provided when plan was revoked |
Clinical Content
| Field | Type | Description |
|---|---|---|
version |
int |
Content version, incremented by server on updates |
title |
string |
Plan title |
description |
string |
Plan description |
zones |
array |
List of clinical zones |
Zone
| Field | Type | Description |
|---|---|---|
condition |
string |
Clinical condition for this zone |
action |
string |
Recommended action |
level |
string |
One of: green, yellow, red |
History Response
Lightweight version of Response used by the History endpoint. Omits id, subject, clinical_content, approval_status, rejection_reason, and revoke_reason. Tracks only audit trail metadata.
| Field | Type | Description |
|---|---|---|
version |
int |
Version number of this history entry |
status |
string |
Status at this version |
updated_at |
string |
ISO 8601 timestamp, when this version was created |
updated_by |
object |
Health personnel who created this version |
comment |
string |
Comment for this version |
Health Personnel
| Field | Type | Description |
|---|---|---|
hpr_number |
string |
HPR identifier |
display_name |
string |
Full name |
Patient
| Field | Type | Description |
|---|---|---|
nin |
string |
National identity number |
Status Lifecycle
A plan can have one of the following statuses:
draft— needs follow upactive— can be shown to the patient, does not say whether the plan is approved or notrevoked— the plan has been withdrawn
Status transitions and who made them are recorded in the version history via updated_by, updated_at, and comment.
Approval Lifecycle
Separate from status, each plan tracks an approval state:
- (none) — no approval process has been initiated
approval_requested— approval has been requestedapproval_given— approval has been grantedapproval_rejected— approval has been rejected
The rejection_reason field is populated on the response when approval is rejected.
How Versioning Works
Each plan has a single version counter:
- Starts at
1when the plan is created. - Incremented by the server on every mutation.
GETendpoints always return the latest version.- Old versions are immutable — once created, a version never changes. Retrieve any past version via
GET /v2/plan/{id}/version/{version}. - Every mutation (except create) requires the current
versionin the request body. If it doesn't match the server's version, you'll get a409 Conflict. This prevents lost updates when multiple users edit the same plan simultaneously.
Endpoints
Create Plan
Creates a new plan.
POST /v2/plan
Request body:
{
"status": "draft",
"comment": "Initial draft",
"subject": {
"nin": "12345678901"
},
"clinical_content": {
"title": "Behandlingsplan",
"description": "Plan for patient with chronic obstructive pulmonary disease",
"zones": [
{
"condition": "Peak flow > 80%",
"action": "Continue current treatment",
"level": "green"
},
{
"condition": "Peak flow 50-80%",
"action": "Increase medication dosage and contact GP within 24 hours",
"level": "yellow"
},
{
"condition": "Peak flow < 50%",
"action": "Call emergency services immediately",
"level": "red"
}
]
}
}
Responses:
| Code | Description |
|---|---|
201 Created |
Plan created, returns {id, version} |
422 Unprocessable Entity |
Invalid JSON |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 1
}
curl
curl -X POST https://planer.dev.nhn.no/v2/plan \
-H "Content-Type: application/json" \
-d '{
"status": "draft",
"comment": "Initial draft",
"subject": {
"nin": "12345678901"
},
"clinical_content": {
"title": "Behandlingsplan",
"description": "Plan for patient with chronic obstructive pulmonary disease",
"zones": [
{
"condition": "Peak flow > 80%",
"action": "Continue current treatment",
"level": "green"
},
{
"condition": "Peak flow 50-80%",
"action": "Increase medication dosage and contact GP within 24 hours",
"level": "yellow"
},
{
"condition": "Peak flow < 50%",
"action": "Call emergency services immediately",
"level": "red"
}
]
}
}'
Get Latest Plan
Returns the latest version of a plan by ID (from the mutation side).
GET /v2/plan/{id}/$latest
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
Responses:
| Code | Description |
|---|---|
200 OK |
Returns plan object |
400 Bad Request |
Invalid UUID |
404 Not Found |
Plan not found |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 3,
"status": "active",
"updated_at": "2024-03-15T12:00:00Z",
"updated_by": {
"hpr_number": "123456789",
"display_name": "Dr. Hansen"
},
"subject": {
"nin": "12345678901"
},
"approval": {
"status": ""
},
"clinical_content": {
"version": 0,
"title": "Behandlingsplan",
"description": "Plan for patient with chronic obstructive pulmonary disease",
"zones": [
{
"condition": "Peak flow > 80%",
"action": "Continue current treatment",
"level": "green"
},
{
"condition": "Peak flow 50-80%",
"action": "Increase medication dosage and contact GP within 24 hours",
"level": "yellow"
},
{
"condition": "Peak flow < 50%",
"action": "Call emergency services immediately",
"level": "red"
}
]
}
}
curl
curl https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000/$latest
Update Content
Updates the clinical content of a plan, creating a new version. The version field must match the current server version; otherwise a 409 Conflict is returned.
POST /v2/plan/{id}/$update-content
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
Request body:
{
"version": 1,
"status": "draft",
"comment": "Updated treatment zones",
"clinical_content": {
"title": "Updated Behandlingsplan",
"description": "Revised plan for patient with chronic obstructive pulmonary disease",
"zones": [
{
"condition": "Peak flow > 80%",
"action": "Continue current treatment",
"level": "green"
},
{
"condition": "Peak flow 50-80%",
"action": "Increase medication dosage and contact GP within 24 hours",
"level": "yellow"
},
{
"condition": "Peak flow < 50%",
"action": "Call emergency services immediately",
"level": "red"
}
]
}
}
Responses:
| Code | Description |
|---|---|
200 OK |
Content updated, returns {id, version} |
400 Bad Request |
Invalid UUID |
404 Not Found |
Plan not found |
409 Conflict |
Version mismatch — plan was modified by another user; includes Location header pointing to $latest |
422 Unprocessable Entity |
Invalid JSON or missing version field |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 2
}
curl
curl -X POST https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000/$update-content \
-H "Content-Type: application/json" \
-d '{
"version": 1,
"status": "draft",
"comment": "Updated treatment zones",
"clinical_content": {
"title": "Updated Behandlingsplan",
"description": "Revised plan for patient with chronic obstructive pulmonary disease",
"zones": [
{
"condition": "Peak flow > 80%",
"action": "Continue current treatment",
"level": "green"
},
{
"condition": "Peak flow 50-80%",
"action": "Increase medication dosage and contact GP within 24 hours",
"level": "yellow"
},
{
"condition": "Peak flow < 50%",
"action": "Call emergency services immediately",
"level": "red"
}
]
}
}'
Set Draft
Transitions the plan status to draft.
POST /v2/plan/{id}/$draft
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
Request body:
{
"version": 3,
"comment": "Returned to draft for revision"
}
Responses:
| Code | Description |
|---|---|
200 OK |
Status changed, returns {id, version} |
400 Bad Request |
Invalid UUID |
404 Not Found |
Plan not found |
409 Conflict |
Version mismatch — plan was modified by another user; includes Location header pointing to $latest |
422 Unprocessable Entity |
Invalid JSON or missing version field |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 4
}
curl
curl -X POST https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000/$draft \
-H "Content-Type: application/json" \
-d '{
"version": 3,
"comment": "Returned to draft for revision"
}'
Activate Plan
Transitions the plan status to active.
POST /v2/plan/{id}/$activate
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
Request body:
{
"version": 1,
"comment": "Plan approved and activated"
}
Responses:
| Code | Description |
|---|---|
200 OK |
Status changed, returns {id, version} |
400 Bad Request |
Invalid UUID |
404 Not Found |
Plan not found |
409 Conflict |
Version mismatch — plan was modified by another user; includes Location header pointing to $latest |
422 Unprocessable Entity |
Invalid JSON or missing version field |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 2
}
curl
curl -X POST https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000/$activate \
-H "Content-Type: application/json" \
-d '{
"version": 1,
"comment": "Plan approved and activated"
}'
Revoke Plan
Transitions the plan status to revoked. The comment is stored as the revoke reason.
POST /v2/plan/{id}/$revoke
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
Request body:
{
"version": 2,
"comment": "Plan no longer applicable"
}
Responses:
| Code | Description |
|---|---|
200 OK |
Status changed, returns {id, version} |
400 Bad Request |
Invalid UUID |
404 Not Found |
Plan not found |
409 Conflict |
Version mismatch — plan was modified by another user; includes Location header pointing to $latest |
422 Unprocessable Entity |
Invalid JSON or missing version field |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 3
}
curl
curl -X POST https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000/$revoke \
-H "Content-Type: application/json" \
-d '{
"version": 2,
"comment": "Plan no longer applicable"
}'
Request Approval
Initiates the approval process for the plan.
POST /v2/plan/{id}/$approval-requested
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
Request body:
{
"version": 2,
"comment": "Submitting for approval"
}
Responses:
| Code | Description |
|---|---|
200 OK |
Approval requested, returns {id, version} |
400 Bad Request |
Invalid UUID |
404 Not Found |
Plan not found |
409 Conflict |
Version mismatch — plan was modified by another user; includes Location header pointing to $latest |
422 Unprocessable Entity |
Invalid JSON or missing version field |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 3
}
curl
curl -X POST https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000/$approval-requested \
-H "Content-Type: application/json" \
-d '{
"version": 2,
"comment": "Submitting for approval"
}'
Give Approval
Grants approval for the plan.
POST /v2/plan/{id}/$approval-given
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
Request body:
{
"version": 3,
"comment": "Plan reviewed and approved"
}
Responses:
| Code | Description |
|---|---|
200 OK |
Approval given, returns {id, version} |
400 Bad Request |
Invalid UUID |
404 Not Found |
Plan not found |
409 Conflict |
Version mismatch — plan was modified by another user; includes Location header pointing to $latest |
422 Unprocessable Entity |
Invalid JSON or missing version field |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 4
}
curl
curl -X POST https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000/$approval-given \
-H "Content-Type: application/json" \
-d '{
"version": 3,
"comment": "Plan reviewed and approved"
}'
Reject Approval
Rejects the plan's approval request. The comment is stored as the rejection reason.
POST /v2/plan/{id}/$approval-rejected
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
Request body:
{
"version": 3,
"comment": "Missing required assessment data"
}
Responses:
| Code | Description |
|---|---|
200 OK |
Approval rejected, returns {id, version} |
400 Bad Request |
Invalid UUID |
404 Not Found |
Plan not found |
409 Conflict |
Version mismatch — plan was modified by another user; includes Location header pointing to $latest |
422 Unprocessable Entity |
Invalid JSON or missing version field |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 4
}
curl
curl -X POST https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000/$approval-rejected \
-H "Content-Type: application/json" \
-d '{
"version": 3,
"comment": "Missing required assessment data"
}'
Get Plan
Returns the latest version of a plan by ID.
GET /v2/plan/{id}
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
Responses:
| Code | Description |
|---|---|
200 OK |
Returns plan object |
400 Bad Request |
Invalid UUID |
404 Not Found |
Plan not found |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 3,
"status": "active",
"updated_at": "2024-03-15T12:00:00Z",
"updated_by": {
"hpr_number": "123456789"
},
"subject": {
"nin": "12345678901"
},
"clinical_content": {
"version": 0,
"title": "Behandlingsplan",
"description": "Plan for patient with chronic obstructive pulmonary disease",
"zones": [
{
"condition": "Peak flow > 80%",
"action": "Continue current treatment",
"level": "green"
},
{
"condition": "Peak flow 50-80%",
"action": "Increase medication dosage and contact GP within 24 hours",
"level": "yellow"
},
{
"condition": "Peak flow < 50%",
"action": "Call emergency services immediately",
"level": "red"
}
]
},
"approval_status": "approval_given",
"rejection_reason": "",
"revoke_reason": ""
}
curl
curl https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000
Get Plan Version
Returns a specific historical version of a plan by ID and version number. The returned version is immutable — it reflects the plan exactly as it was at that point in time.
GET /v2/plan/{id}/version/{version}
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
version |
Version number (int) |
Responses:
| Code | Description |
|---|---|
200 OK |
Returns plan object at version |
400 Bad Request |
Invalid UUID or version format |
404 Not Found |
Plan or version not found |
Example response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 2,
"status": "draft",
"updated_at": "2024-03-15T11:00:00Z",
"updated_by": {
"hpr_number": "123456789"
},
"subject": {
"nin": "12345678901"
},
"clinical_content": {
"version": 0,
"title": "Updated Behandlingsplan",
"description": "Revised plan for patient with chronic obstructive pulmonary disease",
"zones": [
{
"condition": "Peak flow > 80%",
"action": "Continue current treatment",
"level": "green"
},
{
"condition": "Peak flow 50-80%",
"action": "Increase medication dosage and contact GP within 24 hours",
"level": "yellow"
},
{
"condition": "Peak flow < 50%",
"action": "Call emergency services immediately",
"level": "red"
}
]
},
"approval_status": "",
"rejection_reason": "",
"revoke_reason": ""
}
curl
curl https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000/version/2
Get Plan History
Returns all historical versions of a plan by ID. Returns an empty array if the plan is not found.
GET /v2/plan/{id}/history
Path parameters:
| Parameter | Description |
|---|---|
id |
Plan UUID |
Responses:
| Code | Description |
|---|---|
200 OK |
Returns array of HistoryResponse objects (empty array if no plan found) |
400 Bad Request |
Invalid UUID format |
Example response:
[
{
"version": 1,
"status": "draft",
"updated_at": "2024-03-15T10:30:00Z",
"updated_by": {
"hpr_number": "123456789"
},
"comment": "Initial draft"
},
{
"version": 2,
"status": "draft",
"updated_at": "2024-03-15T11:00:00Z",
"updated_by": {
"hpr_number": "123456789"
},
"comment": "Updated treatment plan"
},
{
"version": 3,
"status": "active",
"updated_at": "2024-03-15T12:00:00Z",
"updated_by": {
"hpr_number": "123456789"
},
"comment": "Approved after review"
}
]
curl
curl https://planer.dev.nhn.no/v2/plan/550e8400-e29b-41d4-a716-446655440000/history
Search Plans
Search plans by patient NIN and/or status. An optional approval_status filter is also supported. Returns the latest version of each matching plan.
GET /v2/plan?patient={nin}&status={status}&approval_status={approval_status}
Query parameters:
| Parameter | Required | Description |
|---|---|---|
patient |
true | Patient NIN |
status |
false | One of: draft, active, revoked |
approval_status |
false | One of: pending, approved, rejected |
Responses:
| Code | Description |
|---|---|
200 OK |
Returns array of matching plans (empty array if no matches) |
400 Bad Request |
Missing patient parameter, invalid status, or invalid approval_status |
Example response:
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"version": 3,
"status": "active",
"updated_at": "2024-03-15T12:00:00Z",
"updated_by": {
"hpr_number": "123456789"
},
"subject": {
"nin": "12345678901"
},
"clinical_content": {
"version": 0,
"title": "Behandlingsplan",
"description": "Plan for patient with chronic obstructive pulmonary disease",
"zones": [
{
"condition": "Peak flow > 80%",
"action": "Continue current treatment",
"level": "green"
},
{
"condition": "Peak flow 50-80%",
"action": "Increase medication dosage and contact GP within 24 hours",
"level": "yellow"
},
{
"condition": "Peak flow < 50%",
"action": "Call emergency services immediately",
"level": "red"
}
]
},
"approval_status": "approval_given",
"rejection_reason": "",
"revoke_reason": ""
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"version": 1,
"status": "draft",
"updated_at": "2024-03-14T09:00:00Z",
"updated_by": {
"hpr_number": "987654321"
},
"subject": {
"nin": "12345678901"
},
"clinical_content": {
"version": 0,
"title": "Diabetesplan",
"description": "Plan for patient with type 2 diabetes",
"zones": [
{
"condition": "Blood glucose 4-7 mmol/L",
"action": "Continue current treatment",
"level": "green"
},
{
"condition": "Blood glucose 7-11 mmol/L",
"action": "Adjust diet and monitor closely",
"level": "yellow"
},
{
"condition": "Blood glucose > 11 mmol/L",
"action": "Contact GP immediately",
"level": "red"
}
]
},
"approval_status": "",
"rejection_reason": "",
"revoke_reason": ""
}
]
curl
# Get all plans for patient:
curl "https://planer.dev.nhn.no/v2/plan?patient=12345678901"
# Get all plans for patient with status 'draft':
curl "https://planer.dev.nhn.no/v2/plan?patient=12345678901&status=draft"
# Get all plans for patient with approval_status 'pending':
curl "https://planer.dev.nhn.no/v2/plan?patient=12345678901&approval_status=pending"
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 /v2/. This provides scalability and consistency guarantees without exposing the CQRS pattern to API consumers.