Profile server API
FAQ › Profile server › API · Nederlands
What is it?
The profile server (server/profile, axum) answers HTTP requests from four kinds of clients:
- The EUZOS PC (the kernel): a plain-text line protocol (
key=valuelines, first line a status word), with a signed profile. - The EUZOS phone app: JSON under
/api/app/v2/, with bearer tokens. - Workplace servers (EUZOS Server): the line protocol under
/api/workplace/v1/. - Agents and admins: JSON agent APIs for DriverEnricher and BrowserFeedback (admin key), HTML pages (the web fallback, registration, the admin page).
Next to it, the download server download.euzos.eu (server/download) checks download tokens for Caddy.
Every route has an English path. The old Dutch paths (from before 27-10-2026) are still served by server/profile/src/legacy.rs with their exact old wire format, because production PCs, workplace servers and the phone app still call them.
How do I…?
…find the handler for a path?
The router is euzos_profileserver::router in server/profile/src/lib.rs; it merges admin_page::router() (admin_page/mod.rs) and legacy::router() (legacy.rs). The tables below give the handler as crate::module::fn for every route.
…call the API from a PC?
Use the line protocol: GET/POST with ?token=<pc token> (64 hex) where needed, a text/plain body of key=value lines, and read the first line of the response (ok, fout, wacht, verlopen, afgemeld, …). The PC first gets a PC token by logging in (QR code via /api/session/{id}, or /api/pc/login, /api/pc/entra, /api/pc/reconnect).
…call the API from the app?
Send JSON with Authorization: Bearer <token>: an eza_… token from POST /api/app/v2/sign-in (password sign-in, 15 minutes, in memory) to pair a phone and manage devices, or an ezd_… device token from pairing (valid until revoked) to view and confirm login requests and log out PCs.
…use the agent APIs?
Set admin_token_file in the configuration, then send Authorization: Bearer <admin key> to /api/driverenricher/v2/… or /api/browserfeedback/v2/…. Without that file these APIs answer 404.
…know whether a feature is switched off?
Many routes answer 404 (fout\nonbekend pad\n) when their feature is off in the configuration: /api/pc/login (pc_password = no), /api/pc/entra (no entra_client_id), the vault (no vault_key_file), /api/apps/token (no download_key_file), report endpoints (reports = no), agent APIs (no admin_token_file), /beheer (admin_page = no).
Details
The PC line protocol
- Content type
text/plain; charset=utf-8. - First line: the status word.
ok(success),fout(error, followed by a text line orreden=<code>),wacht(waiting for the phone),verlopen(expired, withreden=),afgemeld(logged out, withreden=),beschikbaar/niet_beschikbaar(app catalogue). - Then
key=valuelines. Unknown keys are skipped by both sides; a known key twice is an error. - The status words, keys, query parameter names (
pc=,voorkeur=,intrekken=,token=) and values are the same on the English and the legacy routes and stay Dutch: they are a wire format that the kernel checks. - Errors from these endpoints have the form
fout\nreden=<code>\n(some older onesfout\n<text>\n), with codes such aste_vaak(+wacht=<s>),ongeldig,te_groot,database,druk,onjuist,andere_site.
The signed profile
After a successful login (QR approval, password, Microsoft, reconnect) the PC gets, in this order: ok, versie=2 (versie=1 for the old 8.5 format without PC secret), omgeving=, server=, sessie=, pc= (hash of the PC secret), soort= (zakelijk/prive), naam= (the e-mail address), weergavenaam=, kleur= (6 hex), taal=, the settings lines (see below), optionally beheer= (admin policy), item= lines (files), optionally methode=wachtwoord|microsoft, optionally the offline lines (offline-dagen=, offline-token=, account=), token= (the PC token, 64 hex), uitgegeven=, verloopt=, sleutel-id=, and last handtekening= (Ed25519). Everything before handtekening= is signed with the prefix EUZOS-profiel-v2\0 (pc::profile_response_with). The PC checks it with the public key from \EOS\FW\PROFILE.TXT (key=, legacy sleutel=).
item= lines: item=<map|bestand>\t<name>\t<count>\t<when> (at most 64).
Settings the PC writes back
POST /api/profile/settings takes key=value lines from an allow list (settings.rs), for example balk=boven|onder, thema=donker|licht|automatisch, toetsenbord=…, schermtoetsenbord=…, touchpad, mouse and touch options, screensaver=, screensaver-wachttijd=, vergrendelen=, recent=, taal=, kleur=, browser-zoekmachine=, browser-favoriet=, browser-engine=euzos|chromium, browser-site=, mail-account=, display= (per external monitor: on/off, extend/mirror, left/right, resolution, main display; legacy scherm= still accepted), display-layout= (the display profiles: window places per screen set-up; docs/DISPLAY.md), desktop-background=default|<32 hex>,<fill|fit|center> (Settings › Background; the picture itself goes sealed to /api/profile/background, see below). The keys and values stay Dutch (pc protocol), except screensaver-animatie=, which takes logo|aurora|stars|clock (legacy sterren|klok still accepted). They come back signed in the next profile. Never a password or a token.
Desktop background (/api/profile/background)
With the pc token, only when the vault is on (otherwise 404, like an older server): GET /api/profile/background (ok + id=, size=, changed= if there is one), PUT /api/profile/background/<32 hex> (the blob sealed by the pc, at most 1 MiB; the id must be the one desktop-background= names, otherwise 409; 413 above the limit, checked before the body is read), GET /api/profile/background/<32 hex> (the blob, or 204) and DELETE /api/profile/background. One per profile; another id or default in the setting deletes it. Schema 20 (backgrounds). Deploy the server before the kernels. Details: docs/PROFILE-PROTOCOL.md, "Desktop background on the server".
JSON (app and agents)
- App errors:
{"error": "<text>"}or{"error": "<text>", "code": "<code>"}with codes such aste_vaak,onjuist,niet_aangemeld,verlopen,intern, and the account status codes for 403. - On the legacy routes (
/api/app/v1/…, the unversioned agent paths) a middleware (legacy::translate) renames the JSON field names and query parameters: Dutch → English in the request, English → Dutch in the response. Values (codes, statuses, texts) are not translated.
Signed messages
| Message | Signed by | Lines |
|---|---|---|
EUZOS-koppelen-v1 (pairing) | phone chip key (ECDSA P-256) | omgeving=, server=, gebruiker=, uitdaging=, sleutel= (SHA-256 of the public key) |
EUZOS-inloggen-v1 (PC login) | phone chip key | omgeving=, server=, sessie=, pc=, pcip=, profiel=, tijd=, uitdaging=, apparaat= |
EUZOS-beheer-v1 (admin page login) | phone chip key | omgeving=, server=, sessie=, beheerip=, gebruiker=, tijd=, uitdaging=, apparaat= |
EUZOS-koppelcode-v1 | — (hash) | the 6-digit code PC and phone both show: SHA-256 over the prefix, session id and hash of the PC secret |
EUZOS-profiel-v2 | server signing key (Ed25519) | the signed profile above |
EUZOS-werkplek-aanmelden-v1 | workplace key (Ed25519) | omgeving=, server=, sleutel=, uitdaging=, tijd=, lan=, tls=, naam=, handtekening= |
EUZOS-downloadtoken-v1 | download key (Ed25519) | the token ezdl1.<kid>.<app>.<version>.<exp>.<jti>.<account>.<signature> |
Token prefixes
eza_ (app password sign-in), ezd_ (paired phone), eze_ (workplace server, 1 hour, memory only), ezdl1. (download token, 2 hours); the PC token is 64 hex without prefix.
Common protections
- Body limit: 16 KiB by default; settings 2 × 48 KiB; BrowserFeedback reports a little over 512 KB (read by the handler itself); the desktop background 1 MiB (read by the handler itself; a
Content-Lengthabove it is refused before reading). - CSRF (
lib::same_site): any non-GET/HEAD/OPTIONS request withSec-Fetch-Siteother thansame-origin/none, or a foreignOrigin, is refused with 403fout\nreden=andere_site\n. The PC and app send neither header. - Response headers (
lib::security):Cache-Control: no-store,Referrer-Policy: no-referrer,X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Server: euzos-profielserver. - Client IP: behind Caddy the last address of
X-Forwarded-Forcounts, only from localhost and only with the proxy secret headerX-Euzos-Proxy. - Admin domain gate (
admin_page::gate): on the admin domain only/beheer…,/b/<id>and/.well-known/…exist (/redirects to/beheer); on the profile domain/beheer…redirects (308) to the admin domain until 26-10-2027, afterwards 404. - Unknown path: 404
fout\nonbekend pad\n.
Rate limits (main ones)
Limits are per network (IPv4 address, IPv6 /64, and IPv6 /48 at 4×) plus per token/account/PC. Exceeding them gives 429.
| Area | Limit (from the code) |
|---|---|
| Session polls | 60/min per PC (session + PC secret, or PC token), 1200/min per network; running sessions at most 60 per network, 10,000 in total (hard limit 20,000) |
| Settings | 30 per 5 minutes per PC token |
| PC password login | per account and per PC: 3 free attempts, block after 10; per network 10/30; per /48 40/120; increasing delay, block 15 min (up to 4 h) |
| App sign-in | 10 failures per 15 minutes per IP and per user name |
| Vault | 30 per 5 minutes per PC token, 60 per account |
| App catalogue | 30/min per network |
| Download tokens | 20/hour per account, 60/hour per network |
| Entra login | 30/min per network |
| Offline reconnect | 120/min per network, 10 per token |
| Workplace | 30/min per network for challenge/sign-in; 30 download tokens/hour per workplace; 120 admin proofs/hour per account; registration 10/min per network and 10/hour per account |
| Admin page login | 10 per network and 5 per account per 15 minutes |
| 1/min and 5/day per address; 200/hour overall |
Reference
Columns: Method, Path (English), Legacy path (Dutch, served by legacy.rs; "—" = same path or none), Auth, Request, Response, Handler (euzos_profileserver::…).
PC login and session (line protocol)
| Method | Path | Legacy path | Auth | Request | Response | Handler |
|---|---|---|---|---|---|---|
| GET | /api/session/{id} | /api/sessie/{id} | PC secret (?pc=<32 hex>), session bound to the PC's IP | {id} 32 hex; query pc=, voorkeur=zakelijk|prive, intrekken=1 (QR rotation) | wacht + code=<6 digits> [+ gezien=1]; verlopen + reden= (ingetrokken, gebruikt, geen profiel, …); or the signed profile once. Errors: 400, 403 andere pc, 429, 503 | pc::session |
| GET | /api/files | /api/bestanden | PC token (?token=) | — | ok + item= lines; 401 ongeldig token | pc::files |
| GET | /api/status | — (same) | PC token | — | ok + verloopt=<unix>; or afgemeld + reden= (onbekend, afgemeld, verlopen); 503 on a database error (never afgemeld). Also triggers the Entra re-check. | pc::status |
| GET | /api/logout | /api/afmelden | PC token | — | always ok | pc::logout |
| POST | /api/pc/login | /api/pc/inloggen | none (throttled); off with pc_password = no | lines email=, wachtwoord=, sessie=, pc= (32 hex), pc-id= (32 hex), voorkeur= (optional) | signed profile with methode=wachtwoord (+ offline lines); errors reden=ongeldig, te_vaak (+wacht=), onjuist (401), account + status=<code> (403), geen_profiel (403), te_groot, druk, database | pc_login::login |
| POST | /api/pc/entra | — (same) | Microsoft ID token; off without entra_client_id | lines id_token=, sessie=, pc=, pc-id=, nonce= (optional), taal= (optional) | signed profile with methode=microsoft; errors reden=ongeldig, te_groot, te_vaak, token, verlopen, sleutels, tenant, domein, account, druk | entra::login |
| POST | /api/pc/reconnect | /api/pc/herverbind | offline token | lines offline-token= (64 hex), sessie=, pc= | new signed profile (new reconnect token); 403 reden= ingetrokken, verlopen, geblokkeerd, gewist, uit, onbekend (the PC logs out and wipes its offline vault); 400, 413, 429, 503 | offline::reconnect |
| POST | /api/pc/register | /api/pc/registreer | none; only with registration_open | lines email=, wachtwoord=, voornaam=, achternaam=, woonplaats= (all required), taal=nl|en (optional) | 201 ok + email=<address> (always, no enumeration); 400 reden=ongeldig|email_ongeldig|wachtwoord_kort; 403 gesloten; 413; 429 te_vaak; 503 druk | pc_registration::register |
| POST | /api/pc/verify | /api/pc/verifieer | none | lines email=, code= (6 digits), wachtwoord= | 200 ok; 400 code_onjuist; 401 wachtwoord_onjuist; 409 email_in_gebruik; 429; 503 | pc_registration::verify |
| POST | /api/pc/resend | /api/pc/opnieuw | none | line email= | always 200 ok; 400 ongeldig; 429 te_vaak | pc_registration::resend |
| GET | /api/preference/{id} | /api/voorkeur/{id} | none | session id | one line: zakelijk, prive or empty (the button chosen on the PC; for the web page) | web::preference |
Settings and vault (PC token)
| Method | Path | Legacy path | Auth | Request | Response | Handler |
|---|---|---|---|---|---|---|
| POST | /api/profile/settings | /api/profiel/instellingen | PC token | key=value settings lines (allow list) | ok; 400 fout\n<reason>; 401; 413 te groot; 429; 503 | pc::settings |
| GET | /api/profile/background | — | PC token; off without vault_key_file | — | ok + id=, size=, changed= if this profile has a picture | background::info |
| PUT | /api/profile/background/{id} | — | PC token | the blob sealed by the pc (at most 1 MiB) | ok; 400 reason=invalid, 401, 404, 408, 409 reason=not_current (not the id of desktop-background=), 413 reason=too_large (before reading), 429, 503 | background::put |
| GET | /api/profile/background/{id} | — | PC token | — | 200 the blob as application/octet-stream, or 204 none | background::fetch |
| DELETE | /api/profile/background | — | PC token | — | ok (also if nothing was there) | background::delete |
| GET | /api/vault | /api/kluis | PC token; off without vault_key_file | — | ok + soort=<kind> per secret (no content) | vault::list |
| PUT | /api/vault/{kind} | /api/kluis/{kind} | PC token | raw bytes = the secret | ok; 400, 401, 403 (opslag-sleutel only by the server), 404, 409 (too many, or lager: a lower generation), 413, 429, 503 | vault::set |
| GET | /api/vault/{kind} | /api/kluis/{kind} | PC token | — | 200 secret as application/octet-stream, or 204 none; opslag-sleutel is created on first fetch | vault::fetch |
| DELETE | /api/vault/{kind} | /api/kluis/{kind} | PC token | — | ok (also if nothing was there); opslag-sleutel: 403 since 28-10-2026 (PS-15: crypto-shredding only with /api/app/v2/vault/wipe or the CLI) | vault::delete_secret |
Vault kinds (allow list, wire values): opslag-sleutel (storage key, 32 bytes, server-created), ms-refresh:<32 hex> (a Microsoft refresh token), opslag-versie:<32 hex> (generation of a profile directory, 8 bytes, only goes up). At most 9 secrets per user and profile kind, 8 KiB each; AEAD ChaCha20-Poly1305 under the server key.
Apps and download tokens
| Method | Path | Legacy path | Auth | Request | Response | Handler |
|---|---|---|---|---|---|---|
| GET | /api/apps | — (same) | none (public) | — | ok, then per app naam=, versie=, grootte=, sha256=, beschrijving=<url> (1–4), pakket=<url> (0–4); downloadbasis=<url> when tokens are on; werkplek=/werkplek-tls= lines for PCs in a workplace server's network | apps::list |
| GET | /api/apps/{name} | — (same) | none | app name | beschikbaar + the app's lines, or niet_beschikbaar + the name (always 200) | apps::one |
| POST | /api/apps/token | — (same) | PC token; off without download_key_file | lines app=, versie= (must be the catalogue version) | ok, token=ezdl1.…, verloopt=<unix>, duur=<s> | download_token::fetch |
Workplace servers (line protocol)
| Method | Path | Legacy path | Auth | Request | Response | Handler |
|---|---|---|---|---|---|---|
| POST | /api/workplace/v1/challenge | /api/werkplek/v1/uitdaging | registered workplace key | sleutel=<64 hex> | ok, uitdaging= (60 s, one-time) | workplace::challenge |
| POST | /api/workplace/v1/sign-in | /api/werkplek/v1/aanmelden | signature with the workplace key | the signed message EUZOS-werkplek-aanmelden-v1 (see above) | ok, werkplek=<id>, token=eze_… (1 hour), verloopt=, servertijd=, downloadsleutel=<kid> <hex> (if tokens are on), organisatie= (if set) | workplace::sign_in |
| POST | /api/workplace/v1/download-token | /api/werkplek/v1/downloadtoken | ?token=eze_… | app=, versie= | like /api/apps/token | workplace::download_token |
| POST | /api/workplace/v1/admin-proof | /api/werkplek/v1/beheerbewijs | PC token of an admin; registered PC; same organisation | werkplek=<id>, pcsleutel=<64 hex> | ok, naam=, lan=, tls=, then the proof lines and handtekening= (valid 10 minutes). Errors: 404 werkplek_onbekend/pc_onbekend; 403 werkplek_ingetrokken, werkplek_zonder_organisatie, pc_ingetrokken, andere_organisatie, geen_beheerder; 429 | workplace_admin::proof |
| GET | /api/workplace/v1/admin-list | /api/werkplek/v1/beheerlijst | PC token | — | ok, then per server werkplek=<id>\t<name>\t<lan>\t<actief|niet actief>\t<installed by>\t<date>\t<external address> | workplace_admin::list |
| POST | /api/workplace/v1/register | /api/werkplek/v1/registreer | PC token of an admin (from the server installer) | sleutel=<64 hex>, naam=, organisatie=<id> (only with more than one), extern=<address> (optional) | ok, werkplek=<id>, organisatie=<id>, organisatie_naam=; errors 401 token, 403 geen_beheerder, 409 kies_organisatie (+ organisatie=<id>\t<name> lines; not for a server admin since 28-10-2026: registered without organization, organisatie= empty + organisatie_kiezen=1), 409 al_geregistreerd, 400 ongeldig, 429 te_vaak | workplace_registration::register |
Reports: DriverEnricher and BrowserFeedback
| Method | Path | Legacy path | Auth | Request | Response | Handler |
|---|---|---|---|---|---|---|
| POST | /api/driverenricher/report | /api/driverenricher/melding | PC token; off with reports = no | report in the DriverEnricher line format (at most 64 KiB, 128 devices; read with euzos_driverenricher::read) | ok, nieuw=<n>, apparaten=<n>; 400, 401, 404, 408, 413, 429, 503 | driverenricher::report |
| GET | /api/driverenricher/v2/list | /api/driverenricher/lijst | admin key | query status=, max= | JSON devices list (most PCs first) | driverenricher::list |
| GET | /api/driverenricher/v2/device/{id} | /api/driverenricher/apparaat/{id} | admin key | — | JSON of one device, with sample, models, after_driver; free text under report_text with "source": "onbetrouwbaar" | driverenricher::device |
| POST | /api/driverenricher/v2/device/{id}/status | /api/driverenricher/apparaat/{id}/status | admin key | JSON {"status": "nieuw|in_behandeling|driver|niet_ondersteund", "version": "v150", "notes": "…"} (version required with driver) | the updated device | driverenricher::status |
| POST | /api/browserfeedback/report | /api/browserfeedback/melding | PC token; off with reports = no | header in line format plus an optional PNG (≤ 512 KB) (euzos_browserfeedback::read) | ok, id=<number> | browserfeedback::report |
| GET | /api/browserfeedback/v2/list | /api/browserfeedback/lijst | admin key | query status=, category= (legacy categorie=), max= | JSON reports (newest first, without screenshot) | browserfeedback::list |
| GET | /api/browserfeedback/v2/{id} | /api/browserfeedback/{id} | admin key | — | JSON of one report, with image_png_base64 if any; address_public | browserfeedback::details |
| GET | /api/browserfeedback/v2/{id}/image.png | /api/browserfeedback/{id}/afbeelding.png | admin key | — | the PNG; 404 if none | browserfeedback::image |
| POST | /api/browserfeedback/v2/{id}/status | /api/browserfeedback/{id}/status | admin key | JSON {"status": "nieuw|in_behandeling|opgelost|niet_op_te_lossen|dubbel", "version": "0.6.1", "notes": "…"} (version required with opgelost) | the updated report | browserfeedback::status |
Agent JSON field names (English / legacy Dutch): error/fout, notice/let_op, devices/apparaten, reports/meldingen, report_text/melding_tekst, source/bron, description/omschrijving, sample/voorbeeld, samples/voorbeelden, models/modellen, after_driver/na_driver, id, key/sleutel, kind/soort, pcs, accounts, first/eerste, last/laatste, last_build/laatste_build, status, version/versie, notes/notities, changed/gewijzigd, max, pc, account, time/tijd, build, arch, browser, language/taal, address/adres, address_public/adres_publiek, complete/volledig, category/categorie, viewport_w/viewport_b, viewport_h, zoom, http_status, load_time/laadtijd, render_time/opmaaktijd, bytes, comment/opmerking, errors/fouten, has_image/heeft_afbeelding, image_bytes/afbeelding_bytes, image_png_base64/afbeelding_png_base64 (legacy::AGENT). This is the rename table of the legacy middleware, not a per-response schema; which of these fields a given response carries: (to verify) per handler.
Phone app (JSON, /api/app/v2/)
| Method | Path | Legacy path | Auth | Request | Response | Handler |
|---|---|---|---|---|---|---|
| GET | /api/app/v2/server | /api/app/v1/server | none | — | environment, server, key_id, public_key, web_fallback, registration_open | app::server |
| POST | /api/app/v2/sign-in | /api/app/v1/aanmelden | none (throttled) | user (e-mail; email also accepted), password | token (eza_…), expires, user, challenge, environment, server; 401 onjuist, 403 status code, 429 te_vaak | app::sign_in |
| POST | /api/app/v2/logout | /api/app/v1/afmelden | eza_ (forgets that sign-in) | — | ok (always) | app::logout |
| POST | /api/app/v2/register | /api/app/v1/registreer | none; only with registration_open | user (or email), password, display_name?, language?, first_name?, last_name?, city? | 201 user, status: "niet_geverifieerd" | app::register |
| POST | /api/app/v2/verify | /api/app/v1/verifieer | none | {email, code, password} or {token, password} | ok, user | app::verify |
| POST | /api/app/v2/verify/resend | /api/app/v1/verifieer/opnieuw | none | email (or user) | ok (always) | app::verify_resend |
| POST | /api/app/v2/email | /api/app/v1/email | eza_ | new | ok (mail sent to the new address) | app::email_change |
| POST | /api/app/v2/devices/challenge | /api/app/v1/apparaten/uitdaging | eza_ | — | challenge (new pairing challenge) | app::pair_challenge |
| POST | /api/app/v2/devices | /api/app/v1/apparaten | eza_ | name, platform (ios|android), public_key (base64 P-256), signature (base64, over EUZOS-koppelen-v1), attestation? (base64, ≤ 8 KiB) | 201 device, device_token (ezd_…), environment, server; at most 10 devices | app::pair |
| GET | /api/app/v2/devices | /api/app/v1/apparaten | eza_ or ezd_ | — | devices: [{device, name, platform, created, last, revoked, this_device}] | app::devices |
| DELETE | /api/app/v2/devices/{id} | /api/app/v1/apparaten/{id} | eza_ (any own device) or ezd_ (only itself: unpair) | — | ok | app::revoke |
| GET | /api/app/v2/session/{id} | /api/app/v1/sessie/{id} | ezd_ | — | session, environment, server, pc, pc_ip, phone_ip, same_network, code, preference, created, expires, challenge, device, user, profiles [{kind, name, display_name, color}]; 410 verlopen | app::session |
| POST | /api/app/v2/session/{id}/confirm | /api/app/v1/sessie/{id}/bevestig | ezd_ + chip signature | profile, time, signature (base64, over EUZOS-inloggen-v1) | ok, pc_ip, profile | app::confirm |
| POST | /api/app/v2/session/{id}/reject | /api/app/v1/sessie/{id}/weiger | ezd_ | — | ok | app::reject |
| GET | /api/app/v2/pc-sessions | /api/app/v1/pcsessies | eza_ or ezd_ | — | pc_sessions: [{ref, kind, pc_ip, device, created, expires, logged_out, method}] | app::pc_sessions |
| POST | /api/app/v2/pc-sessions/{ref}/logout | /api/app/v1/pcsessies/{ref}/afmelden | eza_ or ezd_ | — | ok (remote logout of that PC; also revokes its offline vault) | app::pc_logout |
| POST | /api/app/v2/vault/wipe | — | eza_ (signed in with the password) | {email?, profile?, confirm} | {ok, deleted}: wipe the vault (incl. opslag-sleutel) of one profile or both; only the owner or an admin of the account's organization; 400 bevestig/ongeldig, 401, 403 geen_recht, 404 (vault off), 429 | vault::wipe_vault |
| GET | /api/app/v2/admin/{id} | /api/app/v1/beheer/{id} | ezd_ | — | kind: "beheer", session, environment, server, user, admin_ip, phone_ip, same_network, text, code, created, expires, challenge, device; 410 verlopen | admin_page::app::request |
| POST | /api/app/v2/admin/{id}/confirm | /api/app/v1/beheer/{id}/bevestig | ezd_ + chip signature | time, signature (base64, over EUZOS-beheer-v1) | ok, admin_ip | admin_page::app::confirm |
| POST | /api/app/v2/admin/{id}/reject | /api/app/v1/beheer/{id}/weiger | ezd_ | — | ok | admin_page::app::reject |
Legacy JSON field names of the app API (legacy::APP): error/fout, environment/omgeving, key_id/sleutel_id, public_key/publieke_sleutel, web_fallback/web_terugval, registration_open/registratie_open, expires/verloopt, user/gebruiker, password/wachtwoord, display_name/weergavenaam, language/taal, first_name/voornaam, last_name/achternaam, city/woonplaats, new/nieuw, challenge/uitdaging, name/naam, signature/handtekening, attestation/attestatie, device/apparaat, device_token/apparaat_token, devices/apparaten, this_device/dit_apparaat, created/gemaakt, last/laatst, revoked/ingetrokken, session/sessie, phone_ip/telefoon_ip, same_network/zelfde_netwerk, preference/voorkeur, profiles/profielen, profile/profiel, kind/soort, color/kleur, time/tijd, pc_sessions/pcsessies, logged_out/afgemeld, method/methode, admin_ip/beheer_ip, text/tekst. Unchanged: code, ok, server, token, email, status, platform, pc, pc_ip, ref.
Web pages (HTML; user-visible paths stay as they are)
| Method | Path | Auth | Request | What | Handler |
|---|---|---|---|---|---|
| GET | / | none | — | start page | web::begin |
| GET | /l/{id} | none | — | the phone page of a QR login without the app (only with web_fallback) | web::phone_page |
| POST | /l/{id}/aanmelden | none | form user, password | sign in on the web page | web::sign_in |
| POST | /l/{id}/bevestig | web cookie | form action=login|reject, profile=zakelijk|prive | approve or reject the PC login | web::confirm |
| POST | /uit | web cookie | form back | log out on the phone page | web::logout |
| GET | /v/{token} | link from the mail | — | e-mail confirmation page | web::verification_page |
| POST | /v/{token} | link from the mail | form password | confirm the address | web::verify |
| GET | /registreer | none | — | registration form (or "closed") | web::register_page |
| POST | /registreer | none; registration_open | form first_name, last_name, city, email, password, repeat | register (same steps as the app) | web::register |
| GET | /.well-known/apple-app-site-association | none | — | iOS universal links (/l/*, /v/* open the app) | web::apple |
| GET | /.well-known/assetlinks.json | none | — | Android app links | web::android |
Admin page (HTML, admin domain only)
See Admin page. Session cookie __Host-euzos_beheer (15 minutes idle, 4 hours max, bound to the IP), CSRF token in every form, POST only from the own site.
| Method | Path | What | Handler |
|---|---|---|---|
| GET | /beheer, /beheer/ | login form (form fields email, password, csrf) or dashboard | admin_page::fetch_begin |
| GET | /beheer/{page} | pages: accounts (?q= search), accounts/<id>, pcsessies, telefoons, apps, downloadtokens, log, status | admin_page::fetch → pages::show |
| POST | /beheer/inloggen | login with e-mail and password, then the QR/confirmation in the app | admin_page::login::login |
| POST | /beheer/annuleer | cancel a waiting login | admin_page::login::cancel |
| POST | /beheer/uit | log out | admin_page::send_core |
| POST | /beheer/accounts/nieuw | create an account | pages::account_new |
| POST | /beheer/accounts/<id>/<action> | blokkeer, deblokkeer, wachtwoord, email, beheer, wis, wis-bevestig (two-step delete, 5 minutes) | pages::account_action |
| POST | /beheer/pcsessies/afmelden | log out a PC (ref, back) | pages::pc_logout |
| POST | /beheer/telefoons/intrek | revoke a phone (id, back) | pages::phone_revoke |
| POST | /beheer/apps/zet | add/replace a catalogue entry | pages::app_set |
| POST | /beheer/apps/wis | remove an app (name) | pages::app_delete |
| POST | /beheer/downloadtokens/intrek | revoke a download token (jti, comment) | pages::token_revoke |
| GET | /b/{id} | the link in the admin QR code (opens the EUZOS app) | admin_page::login::link_page |
Download server (download.euzos.eu)
server/download (euzos-download) runs on localhost behind Caddy's forward_auth. Caddy asks it for every request and passes X-Forwarded-Method, X-Forwarded-Uri, Authorization and X-Forwarded-For; on 200 Caddy serves the file.
| Method | Path (as forwarded) | Auth | Response |
|---|---|---|---|
| GET, HEAD | /<app>/<version>/<file> | Authorization: Bearer ezdl1.… | 200 allowed; otherwise fout\nreden=<code>\n: 401 geen_token, handtekening, verlopen; 403 ingetrokken, ander_pad; 404 pad (other method or path) |
A token is valid if its kid is known (public keys in the key file), the signature matches, app and version equal the path, it has not expired and is not valid too far into the future, and its jti is not on the blocklist (euzos_download::check).
Legacy routes (old → new)
Policy (docs/ENGLISH.md, rule 3): the profile server API has English routes and JSON fields; the old Dutch routes keep their exact old wire format in legacy.rs until every production PC, workplace server and the phone app has moved. The line protocol is byte-identical on both; the JSON APIs are the same handlers with the field names translated. Cross-checked against legacy::router:
| Method | Old (legacy) | New |
|---|---|---|
| GET | /api/sessie/{id} | /api/session/{id} |
| GET | /api/bestanden | /api/files |
| GET | /api/status | /api/status (same, no legacy route) |
| GET | /api/afmelden | /api/logout |
| POST | /api/pc/inloggen | /api/pc/login |
| POST | /api/pc/entra | /api/pc/entra (same, no legacy route) |
| POST | /api/pc/herverbind | /api/pc/reconnect |
| POST | /api/pc/registreer | /api/pc/register |
| POST | /api/pc/verifieer | /api/pc/verify |
| POST | /api/pc/opnieuw | /api/pc/resend |
| POST | /api/profiel/instellingen | /api/profile/settings |
| GET | /api/kluis | /api/vault |
| PUT/GET/DELETE | /api/kluis/{soort} | /api/vault/{kind} |
| GET/POST | /api/apps, /api/apps/token, /api/apps/{naam} | same (no legacy route) |
| POST | /api/werkplek/v1/uitdaging | /api/workplace/v1/challenge |
| POST | /api/werkplek/v1/aanmelden | /api/workplace/v1/sign-in |
| POST | /api/werkplek/v1/downloadtoken | /api/workplace/v1/download-token |
| POST | /api/werkplek/v1/beheerbewijs | /api/workplace/v1/admin-proof |
| GET | /api/werkplek/v1/beheerlijst | /api/workplace/v1/admin-list |
| POST | /api/werkplek/v1/registreer | /api/workplace/v1/register |
| POST | /api/driverenricher/melding | /api/driverenricher/report |
| GET | /api/driverenricher/lijst | /api/driverenricher/v2/list |
| GET | /api/driverenricher/apparaat/{id} | /api/driverenricher/v2/device/{id} |
| POST | /api/driverenricher/apparaat/{id}/status | /api/driverenricher/v2/device/{id}/status |
| POST | /api/browserfeedback/melding | /api/browserfeedback/report |
| GET | /api/browserfeedback/lijst | /api/browserfeedback/v2/list |
| GET | /api/browserfeedback/{id} | /api/browserfeedback/v2/{id} |
| GET | /api/browserfeedback/{id}/afbeelding.png | /api/browserfeedback/v2/{id}/image.png |
| POST | /api/browserfeedback/{id}/status | /api/browserfeedback/v2/{id}/status |
| GET | /api/voorkeur/{id} | /api/preference/{id} |
| GET | /api/app/v1/server | /api/app/v2/server |
| POST | /api/app/v1/aanmelden | /api/app/v2/sign-in |
| POST | /api/app/v1/afmelden | /api/app/v2/logout |
| POST | /api/app/v1/registreer | /api/app/v2/register |
| POST | /api/app/v1/verifieer | /api/app/v2/verify |
| POST | /api/app/v1/verifieer/opnieuw | /api/app/v2/verify/resend |
| POST | /api/app/v1/email | /api/app/v2/email |
| GET/POST | /api/app/v1/apparaten | /api/app/v2/devices |
| POST | /api/app/v1/apparaten/uitdaging | /api/app/v2/devices/challenge |
| DELETE | /api/app/v1/apparaten/{id} | /api/app/v2/devices/{id} |
| GET | /api/app/v1/sessie/{id} | /api/app/v2/session/{id} |
| POST | /api/app/v1/sessie/{id}/bevestig | /api/app/v2/session/{id}/confirm |
| POST | /api/app/v1/sessie/{id}/weiger | /api/app/v2/session/{id}/reject |
| GET | /api/app/v1/pcsessies | /api/app/v2/pc-sessions |
| POST | /api/app/v1/pcsessies/{ref}/afmelden | /api/app/v2/pc-sessions/{ref}/logout |
| GET | /api/app/v1/beheer/{id} | /api/app/v2/admin/{id} |
| POST | /api/app/v1/beheer/{id}/bevestig | /api/app/v2/admin/{id}/confirm |
| POST | /api/app/v1/beheer/{id}/weiger | /api/app/v2/admin/{id}/reject |
The kernel still calls the old routes (/api/sessie/, /api/profiel/instellingen, /api/driverenricher/melding, /api/browserfeedback/melding, /api/werkplek/v1/…; docs/ENGLISH.md).
See also
- CLI · PC login · Phone app · Apps and downloads · Admin page
- docs/PROFILE-PROTOCOL.md (the full protocol)
- docs/WORKPLACE-PROTOCOL.md · docs/WORKPLACE.md
- docs/DRIVERENRICHER.md · docs/BROWSERFEEDBACK.md
- docs/DOWNLOADSERVER.md · docs/OFFLINE-LOGIN.md · docs/WORK-LOGIN.md
- docs/ENGLISH.md ("Profile server API")
EUZOS