Using the PJD ATNA endpoint

A guide for external systems consuming the ATNA audit-event query interface exposed by PJD.API's Service API — what it does, how to authenticate against it, and where its behavior departs from a full IHE ATNA implementation.

FHIR R4 · application/fhir+json · OAuth2 + DPoP (HelseID) · IHE ATNA — Final Text rev. 3.6

Guide status: Draft — for external integration partners Resource focus: AuditEvent (FHIR R4) Base interaction path: /R4/fhir/ Publisher system: PJD.API — Service API surface Audience: External readers, outside Norsk helsenett

Contents

  1. Introduction & scope
  2. Actors & conformance
  3. Where the logs come from
  4. What a record contains
  5. What these logs can be used for
  6. Security & scopes
  7. RESTful capability
  8. Search parameters
  9. Request & response
  10. Error handling
  11. Alignment to IHE ATNA
  12. Environments

01. Introduction & scope

What this endpoint is, and what it is not.

PJD.API is the API host behind Norsk helsenett's document-sharing service for patient health records ("Pasientens journaldokumenter"), built as a FHIR-facing façade in front of an IHE XDS/XCA document-sharing infrastructure. As part of that infrastructure, every document transaction is expected to leave an audit trail, consistent with IHE's Audit Trail and Node Authentication (ATNA) profile.

This guide covers the ATNA log query interface, exposed on the Service API host. It lets an authorized external system retrieve AuditEvent resources that were previously recorded for a patient's documents — for example, to support a healthcare provider's own duty to show a patient who has accessed their records.

The interface does not let a caller submit or create audit events. It is read-only: a search operation that aggregates audit events for a patient across every document registry the patient is known to, and a read operation that fetches one audit event by id.

Who this is for This guide is written for integration partners outside Norsk helsenett who consume the Service API surface — that is, health organizations, not individual citizens. Citizens do not have direct access to the Service API at this time; a health organization's own system is what calls this endpoint, including when it's doing so to show a patient who has accessed their records. Endpoint availability is gated by feature toggle and by-environment configuration — confirm activation and scope issuance with NHN before building against it.

⚠ Scoped to your own health organization A consumer can only retrieve ATNA logs tied to its own health organization's registered document registry — not another organization's, and not a cross-organization view. This scoping is enforced by the token you authenticate with (§06), not something you configure yourself.

About this guide This guide was generated with AI assistance from the underlying source code and configuration, and reviewed and verified by a human before publication.

02. Actors & conformance

How this endpoint fits between a caller and the underlying registries.

Two roles participate in an ATNA log query:

  • Audit Record Consumer (your system). Authenticates with HelseID, holds an ATNA read scope, and calls the Service API to retrieve audit events for a given patient or audit-event id.
  • PJD Service API (this interface). Authenticates and authorizes the request, then returns the matching AuditEvent data as a FHIR Bundle — aggregated across every document source the patient is known to.

The Service API itself does not generate or store audit events. Where that data actually comes from is covered next.

03. Where the logs come from

You query audit events here. You do not create them here.

Audit events aren't generated by this interface — they're recorded elsewhere, at the document registries/repositories a patient's documents actually live in, as a byproduct of document transactions happening there (a document being provided, updated, deleted, or looked up). This interface's role is strictly to let an authorized external system query that already-recorded audit trail for a given patient or event id.

flowchart LR
    DS["Document registries /\nrepositories\n(where transactions happen)"]
    STORE[("Recorded\naudit trail")]
    ATNA["ATNA endpoint\nGET /R4/fhir/AuditEvent"]
    YOU(["External consumer\n(your system)"])

    DS -- "records" --> STORE
    STORE -- "queried by" --> ATNA
    YOU -- "GET, read-only" --> ATNA
    ATNA -- "AuditEvent Bundle" --> YOU

Read-only, by design There is no operation on this interface for submitting or creating an audit event. Whatever your system needs logged happens automatically as a side effect of the document transaction itself, at the system handling that transaction — not by calling this endpoint.

What kinds of events you'll find

Not every kind of activity is represented. Based on what is currently recorded, expect these categories in an AuditEvent result, described in IHE ITI transaction terms:

Event category IHE ITI transaction
Document created / registered ITI-41 — Provide and Register Document Set
Document metadata amended (e.g. a confidentiality/securityLabel change) ITI-42 — Register Document Set, recorded as an amend/update
Document deleted ITI-62 — Delete Document Set
Document/bundle validated Recorded even though nothing is persisted
Registry query ITI-18 — Registry Stored Query
Cross-community query / retrieve (via the national XCA gateway) ITI-38 Cross Gateway Query / ITI-39 Cross Gateway Retrieve
Document retrieved from a repository ITI-43 — Retrieve Document Set

⚠ Plain reads are not in the log today A patient's own read-only document lookups — searching for their document references, or reading document content directly — are not currently represented in the audit trail this endpoint returns. Only the creation, amendment, deletion, validation, and cross-community query/retrieve activity above will show up.

04. What a record contains

Every AuditEvent carries who did what, to whose data, from where, and with what result.

An AuditEvent is a structured record, not a log line — everything below is a real field in the JSON you get back, not something you have to parse out of free text.

Element What it tells you Example / values
type / subtype What kind of event this was — a general lifecycle category, plus the specific IHE ITI transaction type: e.g. access, verify (ISO 21089 lifecycle codes) · subtype: e.g. ITI-41
action Create, Read, Update, Delete, or Execute C · R · U · D · E
recorded When the event was captured 2026-08-21T09:14:02+02:00
outcome / outcomeDesc Whether the underlying transaction succeeded, and why not if it didn't 0 success · 4 minor failure · 8 major failure, plus a description
agent / entity (user) The identified professional or system that acted, when that identity was available on the original transaction Name and provider identifier
entity (client) The organization the caller was authenticated as Parent org number, client id, client name
entity (patient) Whose record the event concerns, when there is a patient context Patient identifier(s), name if available
entity (document) The specific document affected, for document-level transactions Title, MIME type, class code, document/submission-set id, home community
purposeOfEvent The clinical purpose of use behind the access, when it was supplied A coded purpose-of-use value
source Which system produced the event Identified by repository unique id and home community id

⚠ Not every field is always present The user, patient, client-organization and purpose-of-use fields depend on what identity and purpose-of-use information was attached to the original transaction. A system-to-system call with a thinner security context will leave some of these empty rather than populate them with placeholder values — check for a field's presence before relying on it.

05. What these logs can be used for

Read access to a structured trail — here's what that supports in practice.

Everything retrievable through this endpoint is what §04 already describes: a structured, per-patient record of who touched a document, when, from where, and with what outcome. In practice, that supports a handful of concrete use cases:

  • Patient access transparency. A health organization can answer a patient's own question — "who has accessed my documents, and when?" — by searching on their patient identifier and presenting the recorded create, amend, delete, query and retrieve events back to them. This is done on the patient's behalf, by the organization's own system; citizens have no direct API access of their own (see §01).
  • Investigating suspected inappropriate access. When there's a concern that a specific patient's record was accessed without a valid reason, pull every recorded event for that patient and use the user, client-organization and purpose-of-use fields (§04) to establish who accessed it, from which organization, and what purpose they stated.
  • Internal compliance and audit reporting. Build your own organization's access reports for internal governance or an oversight review — the default scope restriction (§06) already confines a search to your own registered home community, so what you retrieve is naturally scoped to your own organization's exposure.
  • Reconciling your own document lifecycle. Cross-check the create, amend and delete events recorded here against your own local system of record, to confirm a submission or update you made was correctly reflected upstream.
  • Following up on a specific event. Given an audit-event id surfaced elsewhere — a support ticket, an internal log correlation — fetch that one record directly by id rather than re-running a broader patient search.

⚠ Bounded by what's actually recorded All of the above is bounded by §03 and §04: this is read-only, and plain document reads and searches aren't currently represented in the trail. Don't build a use case — such as proving a specific read never happened — on data this endpoint doesn't capture.

06. Security & scopes

OAuth2 via HelseID, bound to your client with DPoP.

Every call must present an OAuth2 access token issued by HelseID, sender-constrained with a DPoP proof (RFC 9449) as required across NHN's HelseID-secured APIs. The token is validated against the Service API's own authority/audience pair, distinct from PJD's other two API surfaces.

Setting Test value
Authority https://helseid-sts.test.nhn.no
Audience nhn:phr-svc
Authorization header Authorization: DPoP <access_token>
Proof header DPoP: <signed_proof_jwt>

Scopes

Two scopes govern access to this interface. Which one your client is issued determines whether your organization is confined to its own document registry.

Scope Grants Search restriction
nhn:phr-svc/atna/r/repo Read audit events; required for both search and read-by-id. Alone, restricts GET /AuditEvent to the caller's own registered home community.
nhn:phr-svc/atna/r/repo-test Held alongside the scope above, lifts the home-community restriction on GET /AuditEvent. No restriction — any configured source may be queried.

How the restriction is enforced Without the broader scope, the search endpoint requires source:identifier to resolve to a configured registry's homeCommunityId, and checks that your token's orgnr_parent / orgnr_child claims are on that registry's allow-list. If either check fails, the call is rejected with a FHIR OperationOutcome and HTTP 400.

⚠ Read-by-id is not org-restricted GET /AuditEvent/{id} only requires the base .../atna/r/repo scope — it does not apply the home-community check that GET /AuditEvent applies. Treat that scope as sufficient to fetch any audit event by id once granted, not just events belonging to your own organization, and request it only where that is acceptable.

07. RESTful capability

Two interactions on one resource type.

Search — retrieve audit events for a patient

GET /R4/fhir/AuditEvent

Aggregates AuditEvent resources for a patient across every document source configured in the active deployment environment. Requires patient.identifier. Returns application/fhir+json, a single merged searchset Bundle.

Read — retrieve one audit event

GET /R4/fhir/AuditEvent/{id}

Proxies directly to the active environment's upstream audit repository and returns the single AuditEvent it holds for that id, as application/fhir+json.

Interaction Scope required Feature toggle
search-type on AuditEvent .../atna/r/repo (+ -test for cross-org) AtnaLogRetrieveLogEnabled
read on AuditEvent .../atna/r/repo AtnaLogReadAuditEventEnabled

Both toggles ship false by default; the endpoint returns a bare HTTP 404 with no response body when its toggle is off in the target environment.

08. Search parameters

Accepted on GET /R4/fhir/AuditEvent.

Parameter Repeatable Required Notes
patient.identifier No Yes, in practice Resolved via PIXm across configured sources first; an identifier PIX doesn't recognize returns HTTP 404 rather than an empty Bundle.
date Yes No Forwarded to each upstream source once per occurrence, unvalidated. Use standard FHIR date-search prefixes (e.g. ge2026-01-01).
source No No Forwarded verbatim to each upstream query.
source:identifier No Conditionally FHIR token syntax system|code. Required when your token only carries the base scope — see §06.

⚠ Not implemented home and repository query parameters, common in ATNA-style audit queries, are not wired up in this interface. There is no _count, cursor, or other paging parameter — every matching event is returned in one response.

09. Request & response

Representative calls against the test environment.

Search request

GET /R4/fhir/AuditEvent?patient.identifier=12345678901&source%3Aidentifier=urn%3Aoid%3A2.16.578.1.12.4.3.2.1%7C2.16.578.1.12.4.3.2.1&date=ge2026-01-01 HTTP/1.1
Host: api.service.pjd.test.nhn.no
Authorization: DPoP eyJhbGciOi...
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIs...
Accept: application/fhir+json

Search response — 200 OK

{
  "resourceType": "Bundle",
  "type": "searchset",
  "total": 2,
  "entry": [
    { "resource": {
        "resourceType": "AuditEvent",
        "id": "b1f6...",
        "recorded": "2026-08-21T09:14:02+02:00"
        // … remaining AuditEvent fields as recorded upstream
    }},
    { "resource": { "resourceType": "AuditEvent", "id": "a02c...", "recorded": "2026-07-30T11:02:41+02:00" }}
  ]
}

entry.fullUrl, Bundle.id, Bundle.meta and paging links are intentionally omitted — do not rely on them being present.

Read-by-id request

GET /R4/fhir/AuditEvent/b1f6... HTTP/1.1
Host: api.service.pjd.test.nhn.no
Authorization: DPoP eyJhbGciOi...
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIs...

Returns the upstream repository's AuditEvent body and status verbatim on success.

10. Error handling

All errors except a disabled feature toggle are returned as FHIR OperationOutcome.

Status Condition Body
400 Active environment misconfigured; source:identifier doesn't resolve to a known registry; caller's org claims aren't on that registry's allow-list OperationOutcome
404 patient.identifier not found via PIX OperationOutcome
404 Feature toggle disabled for this endpoint in this environment Empty
502 Every upstream registry query failed (search only) OperationOutcome
{
  "resourceType": "OperationOutcome",
  "issue": [{
    "severity": "fatal",
    "code": "security",
    "diagnostics": "Your organization with orgnr_parent (...) and orgnr_child (...) is not allowed to access ATNA logs for home community (...). You must set the homeCommunityId in the 'source:identifier' query parameter."
  }]
}

⚠ Partial failure is silent If at least one of the patient's registries answers successfully, the search endpoint returns 200 OK with whatever data it collected — failures from other registries are logged server-side only. A 200 response is not proof every registry was actually queried successfully; there is currently no client-visible signal for partial data loss.

11. Alignment to IHE ATNA

Where this interface follows the profile, and where it is a narrower, PJD-specific query façade.

This interface implements Audit Trail and Node Authentication, based on IHE's ATNA profile (ITI TF-1, ch. 9; "Final Text" rev. 3.6). Its Amend/Update event category (§03) follows the semantics defined in IHE's Basic Audit Log Patterns (BALP) content profile. Query access itself is realized with the HL7 FHIR R4 AuditEvent resource and Bundle search pattern, rather than ATNA's original syslog-based transport.

Follows the profile:

  • Access to audit data is itself authenticated and access-controlled, consistent with ATNA's node-authentication intent.
  • Cross-organization access is scoped to a caller's own registered home community by default — mirroring ATNA's separation between nodes.
  • Audit data is exposed as structured FHIR AuditEvent resources, not raw log text.
  • This service acts purely as a query façade in front of the audit-generating registries — it does not itself originate audit events.

Narrower than the full profile:

  • No audit-event submission transaction is exposed — this is read/query access only.
  • No server-side paging; a search can return an unbounded number of events in one response.
  • home / repository parameters are not implemented.
  • Read-by-id does not enforce the same organizational boundary that search does (§06).

Known limitations, in full

  1. Read-only. There is no transaction on this interface for recording new audit events — only for querying ones already recorded elsewhere.
  2. No pagination. Every matching AuditEvent is returned in a single response; there is no _count, cursor, or next link.
  3. home / repository unsupported. These parameters, common to ATNA-style audit queries, are not wired into the search implementation.
  4. Silent partial failure. A 200 response from the search endpoint can still be missing data from registries that failed to answer — see §10.
  5. Inconsistent org scoping. GET /AuditEvent/{id} does not apply the home-community restriction that GET /AuditEvent applies for the base scope.
  6. Minimal Bundle envelope. Response bundles omit Bundle.id, Bundle.meta, entry.fullUrl, and all paging links.
  7. Off by default. Both interactions are gated by a feature toggle that defaults to disabled; confirm activation per environment before integrating.

12. Environments

Four deployment environments; audit data never crosses between them.

NHN runs four environments end to end: DevTest, Test, QA and Production. Each running instance of the Service API is wired to exactly one of these, and only queries the audit store paired with that same environment — a request against the Test host will never return Production audit data, and vice versa.

A naming note In configuration, the "Test" environment is keyed internally as test-1 — that string is only an internal config label carried over from earlier setup, not a distinct fifth environment. The table below uses the four environment names as NHN refers to them; the config key column is only useful if you're cross-referencing appsettings.json.

Environment Config key Service API host (external readers call this)
DevTest devtest api-dev.service.pjd.test.nhn.no
Test test-1 api.service.pjd.test.nhn.no
QA qa api.service.pjd.qa.nhn.no (pattern-inferred)
Production prod api.service.pjd.nhn.no (pattern-inferred)

⚠ DevTest may not have audit data configured The configuration reviewed for this guide has no ATNA audit store wired up for DevTest, unlike Test, QA and Production. Confirm with your NHN integration contact that ATNA log retrieval is actually enabled there before relying on it — as reviewed, calling the ATNA endpoint against DevTest would fail with an HTTP 400 configuration error.

Confirming an inferred host QA and Production Service API hostnames aren't present in the checked-in configuration reviewed for this guide — they're inferred from the same naming pattern used by the confirmed Test and DevTest hosts. Confirm the exact hostname, scope issuance, and feature-toggle state for your target environment with your NHN integration contact before go-live.


PJD.API — Service API surface · AuditEvent (ATNA) query interface Generated from the current PJD.API source — verify against your NHN integration agreement before relying on it operationally.

Søk i Utviklerportalen

Søket er fullført!