Users sync API
The HTTP contract your IdP / HR tooling integrates against.
- Canonical contract
- The model: upsert by email
- Authentication
- Endpoints
- Errors
- Rate limits
- Audit attribution
Canonical contract
The authoritative spec is the OpenAPI document in the repo:
openapi/v1.yaml.
It is enforced against the application in CI via committee-rails — every request and
response is schema-checked, so drift fails the build. This page is an orientation, not a
duplicate; integrate against the YAML.
The model: upsert by email
Users arrive in compliventory exclusively through this API (plus the seed bootstrap) —
there is no in-app user CRUD. The whole contract is upsert by email: POST a user on hire
or attribute change, set active: false on departure. There is deliberately no
snapshot-and-diff sync — compliventory only needs “these people exist and may log in /
be owners”.
emailis the upsert key, matched case-insensitively, stored lowercase.nameis a single full-name field; your directory is the source of truth.roleis not settable via sync — roles are assigned in the admin UI.- Deactivation (
"active": false) blocks sign-in on the user’s next request and removes them from owner pickers; history is kept.
Authentication
Bearer token, minted in /admin/api-tokens (shown once, cvt_ prefix, scope
users:write). Pass it in the Authorization header.
Endpoints
POST /api/v1/users — upsert
curl -X POST https://compliventory.example.com/api/v1/users \
-H "Authorization: Bearer cvt_…" -H "Content-Type: application/json" \
-d '{"email":"jane@corp.example","name":"Jane Doe","active":true}'
Returns the user (201 on create, 200 on update).
GET /api/v1/users — list
Full user list ordered by email. No pagination — this is a company directory, not a feed.
Errors
All 4xx/5xx responses use one envelope; branch on the stable error.code
(unauthorized, validation_failed, …), not on the human-readable message:
{ "error": { "code": "validation_failed", "message": "…", "details": { } } }
Rate limits
Per-IP (600/min) and per-token (3000/min across all IPs) throttles guard /api/v1;
throttled requests get a 429 with the same error envelope and a Retry-After header.
Safelist your egress via RATE_LIMIT_SAFELIST if needed
(Deployment).
Audit attribution
Every audit event emitted during a token-authenticated request carries the token’s identity in its metadata — API writes are always attributable to a consumer.