Every request carries a role, decided when you log in. The role controls two things: which endpoints you may call, and which sensitive fields you are allowed to see. Okouro also has a team-leader capability that is a relationship, not a role.
The four roles
A user has exactly one role, returned as the role field on the user object:
| Role | role value | Typical scope |
|---|---|---|
| Admin | admin | Full control of the account: users, settings, validation, settlement. |
| Operations | operations | Day-to-day operations: clients, invoicing, expense settlement. |
| Executive | executive | Read-oriented oversight: KPIs, margins, forecasts. |
| Consultant | consultant | Self-service: own timesheet, time off, assignments, expenses. |
When you sign up you become the account’s admin. A user named as somebody’s manager gains team-leader powers on top of their role (below).
What each role can do
Rather than memorise a matrix, use the status codes as your guide:
403 Forbiddenmeans your role is not allowed to call that endpoint at all, or is not allowed for that specific record.404 Not Foundmeans the record exists outside your scope, or not at all; Okouro does not distinguish the two.
Some concrete rules that recur across the guides:
- Validate or reject a timesheet: admin only.
- Approve or reject time off: admin only.
- Approve or reject an expense: admin or the requester’s team leader.
- Pay an expense: admin or operations.
- Update a mission budget: admin only.
- Read staffing KPIs: admin or executive.
- Read mission-budget KPIs: admin, operations, or executive.
Team leaders
A user who is named as the manager_id of one or more consultants becomes a team leader for those consultants. This is orthogonal to role: an operations user can be a team leader, and so can another consultant.
Team leaders gain scoped powers over the consultants they manage: they can validate subordinates’ timesheets, approve or reject subordinates’ time off and expenses, and act on their behalf. They never gain these powers over their own records, and their reach is limited to the consultants they manage plus their own.
You can tell whether the current user manages anyone from GET /auth/me: is_manager is true when the user manages at least one consultant.
Field-level visibility
Two money fields are sensitive and are hidden from the consultant role:
| Field | Appears on | Visible to | Hidden from |
|---|---|---|---|
daily_cost | consultant profiles, mission budgets | admin, operations, executive | consultant |
billing_rate | assignments, timesheet lines | admin, operations, executive | consultant |
A hidden field is returned as null, never removed from the response. So the JSON shape is stable across roles; only the value changes. For example, the same assignment looks different depending on who asks:
{ "id": "...", "consultant_id": "...", "billing_rate": "900.00", "status": "active" }
{ "id": "...", "consultant_id": "...", "billing_rate": null, "status": "active" }
The second response is what a consultant receives. Margin figures on mission budgets and quote profitability follow the same rule (masked from consultant).
Account isolation
Every record belongs to an account (your tenant). A token only ever reaches records in its own account; there is no way to address another account’s data, and cross-account reads return 404. You never pass an account id on requests; it is derived from your token.
Designing around roles
- Treat
403as “wrong role”, not “transient error”; do not retry. - Expect sensitive numeric fields to be
nullfor consultant-role tokens, and branch your UI or logic on that rather than assuming a number is always present. - If you need to act on a subordinate’s record, the acting user must be that record’s manager (or an admin); a matching role alone is not enough.
See Track time, Request time off, and Manage expenses for the role split in each flow.