Authorization
Roles
| Role | Meaning |
|---|---|
| admin | Billing, team management, interview delete, report retry, full team roster |
| member | Workspace CRUD (positions, candidates, interviews, files, reports read) with exceptions below |
Users must be active. Organizations can be suspended (auth returns forbidden).
Organization scoping
After login, organizationId comes from the authenticated user record in the database—not from query or body parameters. Handlers pass that id into services and repositories so lists and updates cannot cross tenants. Candidate access is different: possession of join token, password, and session grants access to one interview, not org membership.
Permission matrix (effective access)
| Resource / action | Member | Admin |
|---|---|---|
| Positions, candidates, interviews (create/list/edit/cancel) | ✓ | ✓ |
| Delete interview | ✓ | |
| Files (resume upload/parse) | ✓ | ✓ |
| Read reports | ✓ | ✓ |
| Retry report generation | ✓ | |
| Credits, checkout, ledger | ✓ | |
| Team invite, roles, deactivate | ✓ | |
| Team list (full roster) | ✓ | |
| Team list | self only | full roster |
| Notes on candidates | ✓ | ✓ |
| Notes on interviews | own interviews only | all |
| Edit/delete others’ notes | own notes only | any note |
Other surfaces
| Surface | Auth |
|---|---|
| Public health / hello | None |
| Join peek / verify | Join token in URL |
| Candidate interview APIs | Candidate session |
| Bot control APIs | Internal token |
| Stripe webhook | Signature only |
Limits
- No MongoDB row-level security—correct
organizationIdfilters in app code are required. - Members can perform destructive actions allowed by routes (e.g. cancel interview) unless a service blocks it.
- Public join peek may show position title, candidate first name, and branding before password.