2022-06-20
This article outlines the process of reverse-engineering the network requests used by the Norwegian police booking system to create a simplified interface for retrieving available appointment times for passports and ID cards. A lightweight client was implemented to expose only the essential functionality, providing fast access to availability data while avoiding the complexity of the official booking flow. The interface is publicly accessible at app.eipi.dev/pass-og-id.
This small application polls the public booking interface for
passports and ID cards at the Norwegian police. The mechanism relies on
careful querying of the underlying API exposed at
pass-og-id.politiet.no, which provides structured
information about districts, offices, services and available dates. The
site itself remains intentionally sparse: a quiet shelf for things worth
keeping.
All requests originate from a lightweight PHP backend that executes authenticated-but-public endpoints from the Qmatic booking system. Both districts and offices are harvested into a local SQLite database to support faster lookups. Available appointment dates are queried by combining branch identifiers with the appropriate service public ID for either passports or ID cards.
The API offers predictable JSON structures for both hierarchical
branch listings and daily availability windows. By crawling forward a
chosen number of days (dager), the backend returns either
the earliest free appointment or a complete listing of all available
dates. The resulting data are displayed through a minimal HTML
interface.
The system behaves consistently as long as the upstream service maintains its schema. Occasional rate limits may appear, though infrequently. Because the API exposes all district and office metadata, the application can stay self-updating without manual corrections. The structure lends itself well to further expansion, such as caching, historical tracking of availability, or predictive modelling of appointment load.
Two tables are used:
distrikt: stores district ID and name.
avdeling: stores office ID, name and parent
district.
The dager parameter controls the look-ahead window for
availability queries.
Listing services:
curl https://pass-og-id.politiet.no/qmaticwebbooking/rest/
schedule/servicesPublic IDs:
Passport:
d1b043c75655a6756852ba9892255243c08688a071e3b58b64c892524f58d098
ID card:
8e859bd4c1752249665bf2363ea231e1678dbb7fc4decff862d9d41975a9a95a
All districts:
curl https://pass-og-id.politiet.no/qmaticwebbooking/rest/
schedule/branchGroups;servicePublicId=...All dates for an office:
curl https://pass-og-id.politiet.no/qmaticwebbooking/rest/
schedule/branches/{avdeling_id}/dates;
servicePublicId=...;customSlotLength=10<?php
$dager = $_GET['dager'];
$date = date('Y-m-d');
$PublicId =
'd1b043c75655a6756852ba9892255243c08688a071e3b58b64c892524f58d098';
$begynn_link = "https://pass-og-id.politiet.no/
qmaticwebbooking/rest/schedule/branches/";
$slutt_link = ";servicePublicId=" . $PublicId . ";customSlotLength=10";
$db = new SQLite3('itWorks.db');
$db->exec("CREATE TABLE distrikt(id INTEGER PRIMARY KEY,
distrikt_id TEXT, distrikt_navn TEXT)");
$db->exec("CREATE TABLE avdeling(id INTEGER PRIMARY KEY,
distrikt_id TEXT, distrikt_navn TEXT,
avdeling_navn TEXT, avdeling_id TEXT)");
$data = file_get_contents('https://pass-og-id.politiet.no/
qmaticwebbooking/rest/schedule/branchGroups;
servicePublicId=' . $PublicId);
$decoded = json_decode($data, true);
foreach ($decoded as $branch) {
$distrikt_navn = $branch['name'];
$distrikt_id = $branch['id'];
$db->exec("INSERT INTO distrikt(distrikt_id, distrikt_navn)
VALUES('$distrikt_id', '$distrikt_navn')");
foreach ($branch['branches'] as $avd) {
$avdeling_navn = $avd['name'];
$avdeling_id = $avd['id'];
$db->exec("INSERT INTO avdeling(distrikt_id, distrikt_navn,
avdeling_navn, avdeling_id)
VALUES('$distrikt_id', '$distrikt_navn',
'$avdeling_navn', '$avdeling_id')");
}
}
?>
Pass-og-ID Interface. (2025). Appointment availability viewer. Retrieved from https://app.eipi.dev/pass-og-id