Overview
The protokoll export system generates meeting minutes (protokoll) in multiple formats. The JSON format is designed for programmatic consumption by scripts and integrations. It represents a filtered projection of the canonical aggregation hash produced byProtokollAggregationService.
The JSON renderer (ProtokollJsonRenderer) receives the full aggregation hash and a set of content_options, then produces a filtered JSON document with only the requested content. Keys for disabled content are omitted entirely (not set to null), so consumers can use key? checks to detect presence.
Versioning policy
Every JSON export includes a top-levelversion field (currently "2.0").
- Additive changes within a major (2.x): new keys, new enum values, new audit-log actions. These do not bump the version.
- Breaking changes bump the major: renames, removals, type changes, or reorderings of existing keys or values.
Consumers should tolerate unknown keys gracefully. New keys may appear in any 2.x release.
Version history
Timestamps
Every*_at value is an ISO 8601 string with an explicit offset. Since VOT-425 the app runs in the Europe/Oslo zone, so the offset is Oslo’s for that date: +02:00 in summer (CEST) and +01:00 in winter (CET), e.g. "2026-06-12T18:02:00+02:00". Exports generated before VOT-425 used UTC ("2026-06-12T16:02:00Z") for the same instant. Parse the values with an ISO 8601 / RFC 3339 parser and compare them as instants, not as strings. Date-only fields (starts_on, ends_on) are plain YYYY-MM-DD dates and have no offset.
The Markdown, PDF and DOCX covers show the export time and the meeting start/end in Oslo local time (protokoll.time_format, e.g. «12.06.2026 kl. 18:02»).
Top-level structure
Every JSON export contains these keys:Meeting lifecycle fields
Added in 2.0 (VOT-434, additive). They record the formal start of the meeting («Start møte») and its end.
The Markdown, PDF and DOCX covers print the same information («Møtet startet» / «Møtet avsluttet», with the name).
Attendance
Added in 2.0 (VOT-436, additive). Everyone is present until the secretary marks them «Fraværende» (absent); a participant added by mistake once the meeting had started is marked «Feilregistrert» (registered in error) instead of being deleted.
Per participant,
attendance / absent_since give the state at export time, and registered_in_error flags a «Feilregistrert» participant. The Markdown, PDF, DOCX and CSV exports leave «Feilregistrert» participants out of the participant list and the attendance section; the JSON keeps them, flagged.
An absent or «Feilregistrert» participant is not stemmeberettiget. A poll’s own eligible set is frozen when it closes (its snapshot): eligible_voter_count and non_voter_participant_ids reflect who was absent then, while participants[].can_vote is computed at export time.
Audit log
audit_log lists the allowlisted events (ProtokollAggregationService::AUDIT_LOG_ALLOWLIST) in chronological order: meeting.started, meeting.ended, meeting.reopened, poll.opened, poll.closed, poll.round_created, poll_option.withdrawn, agenda_item.manual_result_set, agenda_item.manual_result_cleared, agenda_item.completed, agenda_item.reopened, agenda_item.voting_restarted, and (VOT-436) participant.marked_absent, participant.marked_present, participant.marked_registered_in_error and participant.unmarked_registered_in_error (auditable_id is the participant).
«Start møte» deletes every audit row written while the meeting was being prepared (owner decision, VOT-434), so for a started meeting the log begins with meeting.started. A meeting still being prepared has no lifecycle entries yet (and its rehearsal entries will be deleted when it starts).
metadata of the lifecycle entries:
A reopened meeting shows
meeting.ended followed by meeting.reopened (and, if it was ended again, another meeting.ended); meeting.closed_at / closed_by describe the last end only.
The document and its export record
A generated JSON document never changes. Facts that arise later live on the export record (ProtokollExport), which the web UI’s export list shows next to the files:
Content options filtering
Thecontent_options hash controls which keys appear in the output:
Poll settings modes
Vote detail modes
Thevote_detail option controls how poll rounds appear in each agenda item’s polls array:
full_rounds
All closed rounds included with full detail (options, vote counts, outcome, settings).
summarized_plus_final (default)
Non-final rounds are reduced to summary keys only. The final/decisive round has full detail.
Summary keys: round_number, round_summary, outcome, status.
final_only
Only the decisive round (where latest_for_agenda_item is true) appears, with full detail. Earlier rounds are omitted entirely.
Minimal example
A minimal export with default options for a meeting with one completed binary poll:Key reference
Meeting object
Organization object
Participant object
Agenda item object
Poll object (full detail)
Poll object (summary detail)
When a poll is rendered in summary mode (non-final rounds undersummarized_plus_final):