Authorization & Scopes
Three things decide what an API key can do:
- Scope: whether the key covers the whole organisation or one project.
- Permissions: the list of
verb:resourcegrants stored on the key. - The creator's role: a few time-entry actions also check the Align role of the user who created the key.
Key scopes
| Scope | What it reaches | Who creates it |
|---|---|---|
Organisation (organization) | Every project in the organisation, plus organisation-wide endpoints | An admin, in Settings → Developer → API Keys |
Project (project) | One project only | A manager, developer, tech lead or DevOps engineer, choosing a project they can access. Developers, tech leads and DevOps engineers have no Settings link in the sidebar; they open /admin/developer/keys directly. |
What a project-scoped key can call
A project-scoped key can reach only these paths. Anything else returns 403 scope_violation.
| Path | Notes |
|---|---|
GET /me | |
/projects/{its project id}/... | Entries, releases, members, wiki pages, time entries and summaries of that project. |
/time-entries/{id}/... | Only time entries in its project. Others return 404 not_found. |
/wiki-pages/{id} |
This means a project-scoped key cannot call GET /projects (the list), /entries/{id}, /comments/{id}, /attachments/{id}, /releases/{id} (including /evidence), /users, /webhooks, /integration-links, /integration/time-entries, /audit-events, /search or GET /time-entries. Use an organisation-scoped key for those.
Checking a key
GET /api/v1/me
{
"apiKeyId": "f17d33b9-...",
"scope": "project",
"scopedProjectId": "proj-uuid-...",
"permissions": ["read:entry", "read:project", "write:entry"],
"organizationId": "ec597dd5-..."
}
Check scope and permissions when your integration starts, and fail with a clear message if something it needs is missing.
Permissions
Each key carries a list of permissions. Every endpoint checks one; if the key does not hold it, the request returns 403 forbidden with a message naming the missing permission (for example Missing write:time_entry permission.).
| Resource | Permissions |
|---|---|
| Projects | read:project, write:project, delete:project |
| Entries | read:entry, write:entry, delete:entry |
| Comments | read:comment, write:comment, delete:comment |
| Attachments | read:attachment, write:attachment, delete:attachment |
| Releases (including evidence) | read:release, write:release, delete:release |
| Webhooks | read:webhook, write:webhook, delete:webhook |
| Audit log | read:audit |
| Search | read:search |
| Snapshots | read:snapshot |
| Agreements | read:agreement |
| Wiki | read:wiki, write:wiki, delete:wiki |
| Time entries (including time and cost summaries) | read:time_entry, write:time_entry, delete:time_entry |
| Users | read:user, write:user |
| Project members | read:project_member, write:project_member, delete:project_member |
| Integration links | write:integration_link, delete:integration_link |
| SCIM users | read:scim_user, write:scim_user, delete:scim_user |
GET /integration-links and GET /integration/time-entries/{id} check read:time_entry. POST /integration/time-entries checks write:integration_link. POST /webhooks/{id}/test checks write:webhook (it makes Align send a request; until ALN-731 it checked read:webhook). POST /ci/test-runs checks write:test_run; a key created before that permission existed is also accepted if it holds write:project. DELETE /time-entries/{id} checks delete:time_entry. The OpenAPI spec names the permission each operation checks in x-required-permission.
Default permissions
If you do not click Customize when you create a key, it gets a default for its scope:
| Default | Grants |
|---|---|
| Organization default (organisation keys) | Every permission above except delete:project_member and the SCIM permissions |
| Project default (project keys) | read:project, read:entry, write:entry, read:comment, write:comment, read:attachment, write:attachment, read:release, read:search, read:wiki, read:time_entry, write:time_entry, write:test_run |
The permission editor also offers Third-party app (read-mostly; cannot delete data or read the audit log) and Read only (every read: permission) as starting points.
A key whose permissions is null has full access. Keys created before per-key permissions existed are like this, and so, today, are the keys Align creates when a customer installs a Marketplace app. The key list labels it Full access (legacy). Give it an explicit list to narrow it.
Checks on the creator's role
These actions also check the role of the user who created the key, in addition to the permission:
| Action | Requirement |
|---|---|
POST /time-entries/{id}/approve | Creator is a manager or admin. Otherwise 403 forbidden ("Manager or admin role required to approve time entries."). |
POST /time-entries/{id}/reject | Creator is a manager or admin. Otherwise 403 forbidden. |
POST /time-entries/{id}/unlock | Creator is a manager or admin. Otherwise 403 with reason: "insufficient_role" and details: { "required": "manager_or_admin", "current": "<role>" }. |
POST /time-entries/{id}/submit | Creator owns the time entry, or is a manager or admin. |
| Reading time entries | hourlyRateSnapshot is returned only when the creator is a manager or admin; otherwise it is null. |
Add-ons and features
Some endpoints also depend on the organisation's setup:
- Time tracking. Time-entry, time-summary, cost-summary and integration sync endpoints return
402with reasonfeature_disabledwhen time tracking is turned off for the organisation. - Illumera add-on.
POST /users/invite,POST /users/bulk-invite,GETandPATCH /users/{id}, the project member endpoints, the wiki-page endpoints (exceptGET /projects/{id}/wiki-pages) and the time-entry endpoints (except cost summary and/integration/time-entries) require the organisation to have the Illumera add-on. Without it they return403with{ "reason": "addon_required", "message": "…", "details": { "addon": "illumera", "upgradeUrl": "/settings/integrations" } }. The spec marks these operations withx-requires-addon: illumera. An admin turns it on in Settings → Integrations, under Add-ons.
Error reference
Most errors use the envelope { "reason": "...", "message": "..." }, sometimes with details.
| HTTP | reason | Meaning |
|---|---|---|
401 | missing_key | No Authorization: Bearer or X-API-Key header |
401 | invalid_key | Key not recognised |
401 | key_revoked | Key was revoked |
401 | key_expired | Key has passed its expiry date |
402 | feature_disabled | Time tracking is off for the organisation |
402 | plan_limit | The organisation's plan limit is reached (for example on POST /projects) |
403 | feature_disabled | The Public API is turned off for the organisation |
403 | organization_unavailable | The organisation is archived or deleted |
403 | reconsent_required | The key belongs to a Marketplace app with a permission change the customer has not yet accepted |
403 | installation_suspended | The key belongs to a Marketplace app that was suspended because the re-consent deadline passed |
403 | scope_violation | A project-scoped key tried to reach another project or an organisation-wide endpoint |
403 | forbidden | The key lacks the permission, or the creator lacks the role |
404 | not_found | The resource does not exist or is outside the key's organisation |
423 | suspended | The organisation is suspended for billing |
429 | rate_limit | Rate limit exceeded; see the Retry-After header |