Skip to main content
The admin console is the control surface for everything that touches pay data: uploading salary records, configuring how costs are calculated and disclosed, assigning roles, and reviewing who has seen what. Every route that touches a pay figure writes its audit row in the same database transaction as the read — a read that was not logged is a read that did not commit. Nothing in the admin surface is a privileged back door; the browser page calls the same audited API that any other authenticated client would use.

The admin console

Open /admin in a browser. In a local development deployment you will see a Get a development token button — enter any email address from your directory to mint a token for that user. This button does not exist in production; the route POST /api/v1/dev/token returns 404 outside development mode. From the console you can upload pay data, view and edit configuration, manage roles, and inspect the audit log — all within the permissions your own token carries.

Pay data

CSV upload

Upload a CSV file containing employee pay records. The service recognises common column name aliases from payroll exports so you do not need to reformat the file: employee_email and annual_pay are required. currency defaults to the org’s base_currency. effective_date defaults to today. pay_basis accepts annual or hourly (and common synonyms); it defaults to annual. Upload is all-or-nothing. Any bad row means nothing is written. Every error is reported at once so you can fix them in one pass rather than one upload at a time.
Validate the file without writing anything:
The response shows how many rows would be created or updated, and lists every validation error.
Records are keyed by (employee_email, effective_date). Re-uploading the same effective date corrects that version; a new effective date adds a new version and preserves history.

Manual pay entry

Add or update a single pay record without a CSV file:

Viewing pay records

Returns the latest record per person, masked by default. The amount field is omitted and replaced with a masked display value. To decrypt and view actual amounts:
?reveal=true writes one audit row per person in the result set, in the same transaction as the decryption. Every reveal is on the record. Use ?target=<email> on the audit log to see who has revealed a specific person’s data.

Deleting a person’s pay data

Deletes all versions of a person’s pay record — not just the current one. Use this for leavers and for erasure requests under data protection law. The delete is audited.

Coverage report

Available to Finance and Admin roles. Answers “will estimates be any good?” by showing how much of the directory is covered by pay data and how stale the most recent records are:
stale is true when the newest effective date is older than pay_data_max_age_days (configurable, default 180 days).

Policy configuration

PATCH accepts any subset of the editable fields. All changes are audited with before-and-after values.
participants_see_cost defaults to false deliberately. Shared summaries are the strongest behaviour lever and the biggest cultural risk. Talk to HR and your works council before enabling this. In a two-person meeting, the total minus your own cost is your colleague’s exact pay.
Example patch:

Role management

Available roles are participant, organiser, team_lead, finance, and admin. Team lead assignments include a team_id to scope their report access:
Role grants and revocations are both audited with role.grant and role.revoke actions.

Audit log

Returns audited events in reverse chronological order. Filter by actor, action, or target email:
The audit log records the actor, their role at the time, the action, the target (per-person for bulk accesses — a single “exported 400 rows” entry would not answer “who has seen my salary?”), a timestamp, and the source IP. The log never records an amount — it records that a value was accessed and by whom. Audited actions include:

Directory and HRIS sync

GET /api/v1/admin/directory lists all persons currently in the org directory, including their display name, team, department, active status, and the timestamp of the last sync. POST /api/v1/admin/sync/directory and POST /api/v1/admin/sync/hris trigger an immediate pull rather than waiting for the next scheduled run. Both return the number of records created, updated, and deactivated. Pass ?provider=<name> to sync/directory to target a single configured provider. GET /api/v1/admin/sync-state returns the last-run and last-success timestamps for every adapter, plus a record count and the last error string if any — the backing data for the staleness reminder. To check which adapters have credentials configured:

Audit-on-read guarantee

Every route that touches a pay figure — GET /api/v1/admin/pay?reveal=true, the export, any internal decryption path — writes its audit row in the same database transaction as the read. If the transaction does not commit, neither the read nor the audit row survives. A read that was not logged is a read that did not commit.