Every /api/v2 request passes two separate checks, and every operation page states what each one needs.
| Check | What it is | Who controls it | When it fails |
|---|---|---|---|
| Token scope | An api: ability on the token you sent. | Whoever created the token, or the person who approved the app. | 403 insufficient-token-scope, with required_ability listing the abilities that would have worked. |
| Permission | A permission on the role of the user the token belongs to. | Whoever manages roles. | 403 insufficient-permission, with required_permission naming the missing one. |
The token is only ever a ceiling. Permissions are read again on every request, so taking a permission away from a role narrows existing tokens at once, without touching them.
The two failures have different fixes. A scope failure needs a different token; a permission failure needs a role change. The full order of checks, from the organisation's switch to the record's state, is in Authentication.
How a scope is built
An ability is api:{class} or api:{class}:{prefix}.
- class is
read,writeordestroy. On the API tokens page they are called Read, Write and Delete. - prefix is the resource, spelled as in the permission names, such as
projects,project_managementorwork_types. API tokens maps each prefix to the name the token form shows.
Leave out the prefix and the ability covers every resource of its class: api:write satisfies an operation that asks for api:write:projects.
An app that signs people in with OAuth asks for these same strings as OAuth scopes, except the destroy class, which OAuth never offers.
The class ladder
Each class includes the ones below it: write includes read, and destroy includes both.
| Request | Class required | Satisfied by |
|---|---|---|
GET | read | read, write, destroy |
POST, PATCH | write | write, destroy |
DELETE | destroy | destroy |
Every DELETE needs the destroy class, whether it deletes softly or for good. force=true is accepted on DELETE/projects/{project} alone. There it deletes the project for good, and it checks the same projects::delete permission as a soft delete. See Deleting and restoring.
Some operations list more than one scope. Any one of them is enough. They are the drive operations, where one endpoint serves items owned by a project, a customer, the organisation or a user, plus the comment and upload operations that work items and drive items share.
Token scopes
A token can carry every class with every resource, plus the three unnarrowed abilities api:read, api:write and api:destroy. 82 of them are named by at least one operation, and the table below lists those, with what each covers. api:write and api:destroy are not in it because no operation names them, but api:write satisfies every read and write, and api:destroy every read, write and delete.
| Scope | Resources | Operations |
|---|---|---|
api:read29 scopes | ||
api: | //me | 2 |
api: | /absences | 2 |
api: | /budget-groups | 2 |
api: | /budgets | 2 |
api: | /checkins | 2 |
api: | /company-branches | 2 |
api: | /customer-categories | 2 |
api: | /comments/drive-documents/drive-folders/drive-items/notes/uploads | 12 |
api: | /customer-subscriptions/subscription-items | 4 |
api: | /contact-roles/contacts/customers | 6 |
api: | /events | 2 |
api: | /free-days | 2 |
api: | /furlough-balance-mutations | 2 |
api: | /holidays | 2 |
api: | /timers | 2 |
api: | /invoices | 2 |
api: | /projects/tasks | 2 |
api: | /comments/drive-documents/drive-folders/drive-items/notes/uploads | 12 |
api: | /comments/drive-documents/drive-folders/drive-items/notes/uploads | 12 |
api: | /comments/labels/sprints/subtasks/todos/uploads/work-item-links/work-items | 16 |
api: | /project-links/projects | 4 |
api: | /sales-invoice-rows/sales-invoices | 4 |
api: | /subscription-products | 2 |
api: | /subscription-type-items/subscription-types | 4 |
api: | /worklogs | 2 |
api: | /comments/drive-documents/drive-folders/drive-items/notes/uploads | 12 |
api: | /users | 2 |
api: | /work-types | 2 |
api: | /workflow-statuses/workflow-transitions/workflows | 6 |
api:write27 scopes | ||
api: | /absences | 2 |
api: | /budget-groups | 2 |
api: | /budgets | 2 |
api: | /checkins | 2 |
api: | /company-branches | 2 |
api: | /customer-categories | 2 |
api: | /comments/drive-documents/drive-folders/drive-items/notes | 13 |
api: | /customer-subscriptions/subscription-items | 5 |
api: | /contact-roles/contacts/customers | 7 |
api: | /events | 3 |
api: | /free-days | 2 |
api: | /furlough-balance-mutations | 2 |
api: | /holidays | 2 |
api: | /timers | 2 |
api: | /invoices | 1 |
api: | /projects/tasks | 2 |
api: | /comments/drive-documents/drive-folders/drive-items/notes | 13 |
api: | /comments/drive-documents/drive-folders/drive-items/notes | 13 |
api: | /comments/labels/sprints/subtasks/todos/work-items | 19 |
api: | /project-links/projects | 5 |
api: | /sales-invoice-rows/sales-invoices | 5 |
api: | /subscription-products | 3 |
api: | /subscription-type-items/subscription-types | 5 |
api: | /worklogs | 2 |
api: | /comments/drive-documents/drive-folders/drive-items/notes | 13 |
api: | /work-types | 2 |
api: | /workflow-statuses/workflow-transitions/workflows | 6 |
api:destroy26 scopes | ||
api: | /absences | 1 |
api: | /budget-groups | 1 |
api: | /budgets | 1 |
api: | /checkins | 1 |
api: | /company-branches | 1 |
api: | /customer-categories | 1 |
api: | /comments/drive-documents/drive-folders/drive-items/notes/uploads | 6 |
api: | /customer-subscriptions/subscription-items | 2 |
api: | /contact-roles/contacts/customers | 3 |
api: | /events | 1 |
api: | /free-days | 1 |
api: | /furlough-balance-mutations | 1 |
api: | /holidays | 1 |
api: | /timers | 1 |
api: | /tasks | 1 |
api: | /comments/drive-documents/drive-folders/drive-items/notes/uploads | 6 |
api: | /comments/drive-documents/drive-folders/drive-items/notes/uploads | 6 |
api: | /comments/labels/sprints/subtasks/todos/uploads/work-item-links/work-items | 8 |
api: | /project-links/projects | 2 |
api: | /sales-invoice-rows/sales-invoices | 2 |
api: | /subscription-products | 1 |
api: | /subscription-type-items/subscription-types | 2 |
api: | /worklogs | 1 |
api: | /comments/drive-documents/drive-folders/drive-items/notes/uploads | 6 |
api: | /work-types | 1 |
api: | /workflow-statuses/workflow-transitions/workflows | 3 |
OAuth apps may ask for the read and write abilities only.
Permissions
67 permissions gate /api/v2. They are the same permissions plnnrly checks in the app, so an integration can do exactly what the user behind the token can do there, and no more.
A permission is {prefix}::{action}, such as projects::update. The prefix is usually the one a narrowed scope names, so api:write:projects and projects::update guard the same thing from two sides. Time is the exception: reading worklogs needs the scope api:read:times and the permission hours::read.
| Permission | Resources | Operations |
|---|---|---|
absences:: | /absences | 5 |
budget_groups:: | /budget-groups | 1 |
budget_groups:: | /budget-groups | 1 |
budget_groups:: | /budget-groups | 2 |
budget_groups:: | /budget-groups | 1 |
budgets:: | /budgets | 1 |
budgets:: | /budgets | 1 |
budgets:: | /budgets | 2 |
budgets:: | /budgets | 1 |
checkins:: | /checkins | 2 |
company_branches:: | /company-branches | 1 |
company_branches:: | /company-branches | 1 |
company_branches:: | /company-branches | 2 |
company_branches:: | /company-branches | 1 |
customer_categories:: | /customer-categories | 1 |
customer_categories:: | /customer-categories | 1 |
customer_categories:: | /customer-categories | 2 |
customer_categories:: | /customer-categories | 1 |
customer_subscriptions:: | /customer-subscriptions | 3 |
customer_subscriptions:: | /customer-subscriptions/subscription-items | 2 |
customer_subscriptions:: | /customer-subscriptions/subscription-items | 4 |
customer_subscriptions:: | /customer-subscriptions/subscription-items | 2 |
customers:: | /contact-roles/contacts/customers | 4 |
customers:: | /contact-roles/contacts/customers | 3 |
customers:: | /contact-roles/contacts/customers | 6 |
customers:: | /contact-roles/contacts/customers | 3 |
events:: | /events | 6 |
free_days:: | /free-days | 5 |
furlough:: | /furlough-balance-mutations | 1 |
furlough:: | /furlough-balance-mutations | 2 |
holidays:: | /holidays | 5 |
hours:: | /timers/worklogs | 4 |
invoices:: | /invoices | 1 |
invoices:: | /invoices | 2 |
kanban:: | /projects/tasks | 5 |
project_drive:: | /drive-documents/drive-folders/drive-items/notes | 10 |
project_drive:: | /drive-documents/drive-folders/drive-items/notes | 16 |
project_management:: | /sprints | 5 |
project_management:: | /comments/labels/subtasks/todos/work-item-links/work-items | 21 |
project_management:: | /sprints | 2 |
project_management:: | /comments/labels/subtasks/todos/work-item-links/work-items | 13 |
projects:: | /projects | 2 |
projects:: | /projects | 1 |
projects:: | /project-links | 5 |
projects:: | /projects | 2 |
projects:: | /projects | 1 |
sales_invoices:: | /sales-invoices | 3 |
sales_invoices:: | /sales-invoice-rows/sales-invoices | 2 |
sales_invoices:: | /sales-invoice-rows/sales-invoices | 4 |
sales_invoices:: | /sales-invoice-rows/sales-invoices | 2 |
subscription_products:: | /subscription-products | 2 |
subscription_products:: | /subscription-products | 1 |
subscription_products:: | /subscription-products | 2 |
subscription_products:: | /subscription-products | 1 |
subscription_types:: | /subscription-types | 3 |
subscription_types:: | /subscription-type-items/subscription-types | 2 |
subscription_types:: | /subscription-type-items/subscription-types | 4 |
subscription_types:: | /subscription-type-items/subscription-types | 2 |
users:: | /users | 2 |
work_types:: | /work-types | 1 |
work_types:: | /work-types | 1 |
work_types:: | /work-types | 2 |
work_types:: | /work-types | 1 |
workflows:: | /workflow-statuses/workflow-transitions/workflows | 3 |
workflows:: | /workflow-statuses/workflow-transitions/workflows | 3 |
workflows:: | /workflow-statuses/workflow-transitions/workflows | 6 |
workflows:: | /workflow-statuses/workflow-transitions/workflows | 3 |
| None. The record's own rules still apply. | //checkins/furlough-balance-mutations/me/timers/uploads/worklogs | 15 |
The drive operations name project_drive::access or project_drive::create. For items owned by a customer, the organisation or a user, plnnrly checks the matching customer_drive, organization_drive or user_drive permission instead.
Operations that need no permission
A few operations need a valid token with the right scope and no permission at all. Their pages say so.
- Discovery and GET/me.
- Reading and cancelling an upload ticket.
- Creating, changing and deleting your own worklogs, timers and check-ins, and reading your own furlough mutations. The record's own rules are the gate here, not a permission.
Other people's rows need a permission after all:
- worklogs:
times::update_othersortimes::delete_others, andtimes::update_othersto log time for someone else withon_behalf_of_user_id - furlough mutations:
furlough::view_allorfurlough::manage
Permissions that change what a call does
Some permissions do not decide whether a call runs, but what it may do.
Fields with a permission of their own. Sending one of these fields without its permission answers 403 insufficient-permission naming the field, except is_overtime, which is dropped instead.
| Field | On | Permission | Without it |
|---|---|---|---|
on_behalf_of_user_id | worklogs | times:: | 403 |
debtor_code | customers | billing_settings:: | 403 |
should_email | sales invoices | sales_invoices:: | 403 |
is_approved | furlough mutations | furlough:: | 403 |
is_overtime | worklogs | times:: | Dropped, with an input_ignored warning in meta.warnings. The rest of the write is applied. |
A dropped field does not fail the call, so check meta.warnings after every write.
Permissions that widen which rows you reach. Without them you see and change only your own rows:
times::update_othersandtimes::delete_others: other people's worklogskanban::manage_for_others: other people's kanban tasksevents::manage_for_others: other people's agenda eventsfurlough::view_allandfurlough::manage: other people's furlough mutations
Permissions a record's rules ask for. A few writes check a further permission on the record you write to, and answer 403 forbidden-by-policy without it:
projects::notes: adding a note to a projecttimes::write_on_others_tasks: logging time on a task that belongs to someone else
Each operation page names the permissions that apply to it.
What your organisation exposes
Call GET/ with any API token. It answers one entry per resource your organisation has, each with its permission_prefix, so you can match it with the tables above.
Discovery only leaves out resources of features your organisation does not have. It does not check your token or your role, so a listed resource can still answer 403 for you.
Related
- API tokens: creating a token with the right scopes.
- Authentication: the full order of checks.
- Errors and retries: every problem type and its fix.