Skip to main content

People administration

Endpoints for managing people, their grants, and the sign-in identities linked to them. Every endpoint on this page requires a session cookie belonging to a person whose role is admin. A bearer ID token is not accepted here.

MethodPathPurpose
GET/auth/peopleList people
GET/auth/people/:personIdRead one person with identities and identifiers
POST/auth/peopleCreate a person
PATCH/auth/people/:personIdUpdate a person
POST/auth/people/:personId/linkLink a sign-in identity to a person
DELETE/auth/people/:personId/identity/:uidUnlink a sign-in identity
POST/auth/sync/firebaseReconcile sign-in accounts against people
POST/auth/sync/carddavImport contacts into people
POST/auth/provisionUpdate a person by sign-in identity. Retained for compatibility

The person record

FieldTypeDetail
personIdstringStable identifier for the person
namestringDisplay name
emailstring or nullPrimary address
avatarstring or nullImage URL
statusstringpending, approved or rejected
rolestringuser or admin
projectsarrayProject grants. * grants every project
groupsarrayGroup names
tagsarrayFree-form labels
createdAttimestampSet on creation
lastSeentimestampUpdated by a successful POST /auth/verify

A person is distinct from a sign-in identity. One person holds many identities, one per method they have signed in with. Access is decided on the person.

GET /auth/people

Returns the 200 most recently created people, newest first.

{ "people": [ { "personId": "…", "name": "…", "status": "approved", "role": "user", "projects": [] } ] }

No pagination, filtering or search parameter is accepted. A directory larger than 200 cannot be read in full through this endpoint.

GET /auth/people/:personId

Returns the person, the sign-in identities linked to them, and the identifiers that resolve to them.

FieldDetail
Person fieldsAs the table above
identitiesOne entry per sign-in method, newest link first
identifiersOne entry per address, phone number or provider subject

Each identity carries uid, provider, email, linkedAt, a readable providerLabel, and the provider's subject.

Each identifier carries type, value, verified and source. An identifier with verified: false was imported or typed rather than proved. It suggests a merge and never resolves a sign-in.

ConditionStatusBody
No such person404{"error":"Not found"}

POST /auth/people

FieldTypeRequiredDefault
namestringYes
emailstringNonull
statusstringNopending
rolestringNouser
projectsarrayNo[]
groupsarrayNo[]
tagsarrayNo[]
ConditionStatusBody
Created200{"ok":true,"personId":"…"}
name missing400{"error":"name required"}

A person created here holds no sign-in identity. Link one, or let the person sign in and be matched on a verified identifier.

PATCH /auth/people/:personId

Accepts name, email, avatar, status, role, projects, groups and tags. Any other field is discarded silently.

ConditionStatusBody
Updated200{"ok":true,"syncedFirebase":["…"],"firebaseSyncErrors":[]}
No recognised field supplied400{"error":"no valid fields"}

Changes are pushed to every linked sign-in identity. syncedFirebase lists the identities updated and firebaseSyncErrors lists those that failed with the reason. A 200 with a non-empty error list means the person was updated and at least one identity was not.

ChangeEffect on sign-in
status set to approvedGated endpoints begin accepting the person
status set to rejectedLinked sign-in identities are disabled
projects changedApplies at the next request. Sessions are not re-issued
role set to adminGrants every project and this page

POST /auth/people/:personId/link

Attaches an existing sign-in identity to a person.

FieldTypeRequiredDetail
uidstringYesIdentity to attach
providerstringNoRecorded label. Derived when omitted
emailstringNoRecorded address. Derived when omitted
ConditionStatusBody
Linked200{"ok":true}
uid missing400{"error":"uid required"}
No such person404{"error":"person not found"}
Identity already belongs to another person409{"error":"uid already linked to a different person"}

DELETE /auth/people/:personId/identity/:uid

Unlinks a sign-in identity. The identity is removed from the person, the person is retained.

ConditionStatusBody
Unlinked200{"ok":true}
Identity does not exist, or belongs to another person404{"error":"Identity not found for this person"}

Unlinking does not remove the identifiers that resolve to the person. An address or provider subject already claimed continues to resolve, so an unlinked account can re-link itself by signing in again.

POST /auth/sync/firebase

Walks every sign-in account and reconciles it against people. Takes no body.

{ "ok": true, "scanned": 0, "linked": 0, "createdPeople": 0, "updatedIdentities": 0 }
PropertyDetail
MatchingAn unlinked account is matched to a person by email address alone
CreationAn account matching nothing creates a person with status pending and the tag imported-firebase
DurationSequential per account. Large directories exceed the proxy timeout before finishing
ResumptionNone. A timed-out run leaves partial results and must be restarted

Matching by address alone is weaker than the rule applied at sign-in, where only a proved identifier resolves a person. Run this only against a directory whose addresses are trusted.

POST /auth/sync/carddav

Imports contacts from the configured address books into people. Takes no body.

{ "ok": true, "scanned": 0, "updatedPeople": 0, "createdPeople": 0, "syncedFirebase": 0, "syncErrors": [] }
ConditionStatusBody
Address book access not configured400{"error":"…"}

Existing people are matched by email address and gain the tag carddav-sync. Unmatched contacts create people with status pending. Contacts with no address are skipped.

POST /auth/provision

Retained for compatibility. Updates the person behind a sign-in identity.

FieldTypeRequired
uidstringYes
statusstringNo
projectsarrayNo
rolestringNo
emailstringNo
ConditionStatusBody
Updated200{"ok":true}
uid missing400{"error":"uid required"}

Prefer PATCH /auth/people/:personId. This endpoint does not push changes to linked sign-in identities and does not report what it changed.

Constraints

ConstraintDetail
DeletionNo endpoint deletes a person or an identifier
PaginationNot available on any list
SearchNot available. Lookup is by personId only
Audit trailNot exposed. Changes are not attributed to the admin who made them
ConcurrencyLast write wins. Two admins editing one person overwrite each other