Scopes
Developers

Scopes

The eight permissions an admin can grant an app, what each one allows, and the rules that surprise people.

Scopes

A scope is one permission an admin grants your app. Eight exist. An app holds exactly the ones it was granted and nothing else.

Three rules, stated plainly because every one of them surprises somebody:

  1. Read does not imply write. tickets:read does not let you comment. pulls:read does not let you approve. If your app changes something, it needs the matching write scope, granted separately.
  2. There is no wildcard. No *, no admin, no "all tickets" scope. The eight below are the entire vocabulary.
  3. Scopes are permanent. A scope name never changes meaning and is never removed. New ones may be added; what you built against keeps working.

Scopes are granted by an admin of the workspace, who can grant fewer than you asked for. Always check what you actually got:

python api.me()["scopes"]

Ask for the fewest that work. An app requesting everything is one an admin has to think hard about approving, and it is one whose token is worth far more to whoever eventually steals it.


chat:read — Read chat

Grants. Reading messages, threads and channel information in channels the app belongs to.

Does not grant. Posting anything. Reading channels the app has not been added to — including private ones, direct messages between people, and any channel an admin has not deliberately invited the app into. Membership is the real boundary; this scope only decides whether you may read what membership already lets you see.

Unlocks. GET bot/channels/, GET bot/channels/{channel_id}/messages/


chat:write — Post to chat

Grants. Posting messages and cards, and updating cards the app itself posted.

Does not grant. Reading history — pair it with chat:read if your app needs to look before it speaks. Editing or deleting anyone else's messages. Creating channels, inviting people, or changing channel settings. Posting into a channel the app has not been added to.

Unlocks. POST bot/channels/{channel_id}/messages/, PATCH bot/messages/{message_id}/card/

Card interactions ride on this scope too: a button your app posted calls your app back, and the reply that updates the card is a write.


tickets:read — Read tickets

Grants. Reading tickets, their comments and their history, across the projects in the workspace.

Does not grant. Creating, editing, commenting, or moving anything. Reading another workspace — every request is scoped to the workspace the token belongs to, and that is not a scope you can widen.

Unlocks. GET projects/{project_key}/tickets/, GET projects/{project_key}/tickets/{id}/, GET projects/{project_key}/tickets/{id}/comments/, GET tickets/by-key/{key}/


tickets:write — Write tickets

Grants. Creating and updating tickets, commenting on them, moving them between statuses, and acting as a delegate — being recorded as the app that did something without taking the ticket away from the person it is assigned to.

Does not grant. Reading — a write-only app can create a ticket but cannot list one, which is rarely what anybody wants. Deleting projects, changing workflows, or editing anything about the project itself. Impersonating a person: writes are attributed to the app, always, and that cannot be turned off.

Unlocks. POST projects/{project_key}/tickets/, POST projects/{project_key}/tickets/{id}/comments/, POST projects/{project_key}/tickets/{id}/transition/


repos:read — Read repositories

Grants. Reading branches, commits, tags and file contents through a project's repository connections.

Does not grant. Pushing. Creating branches or tags. Changing repository settings or access. Cloning over git — this is API read access, not a credential for the git server.

Unlocks. GET projects/{project_key}/connections/{connection_id}/browse/, .../browse/{path}/, .../commits/, .../commits/{commit_hash}/, .../tags/, .../compare/

Every route is scoped to one connection, because a repository is reached through the connection that addresses it.

Also required for. registry.image_pushed and registry.image_deleted events. A container image is a build artifact of the same code this scope already covers, so it rides on repos:read rather than on a separate registry scope.


pulls:read — Read pull requests

Grants. Reading pull requests, their reviews, their reviewers and their comments.

Does not grant. Opening, commenting, approving, requesting changes, assigning reviewers, or merging. Reading the underlying repository contents — that is repos:read.

Unlocks. GET projects/{project_key}/pulls/, GET projects/{project_key}/pulls/{number}/, .../comments/, .../reviews/, .../reviewers/


pulls:write — Write pull requests

Grants. Opening and updating pull requests, commenting on them, submitting reviews, and managing the reviewer list.

Does not grant. Merging by itself, where the project requires approvals or a green pipeline — an app is subject to the same rules as a person, and a review from an app is a review, not an override. Pushing commits. Reading, unless you also hold pulls:read.

Unlocks. POST projects/{project_key}/pulls/, POST projects/{project_key}/pulls/{number}/comments/, POST projects/{project_key}/pulls/{number}/reviews/, POST projects/{project_key}/pulls/{number}/reviewers/


events — Receive events

Grants. Creating and managing event subscriptions, holding an outbound event socket, and receiving webhook deliveries.

Does not grant. Reading anything the events refer to. This is the one that catches people out: an app with only events gets told a ticket changed status and cannot then fetch the ticket. Pair it with the read scopes for whatever you intend to reconcile against.

Unlocks. GET/POST bot/subscriptions/, DELETE bot/subscriptions/{subscription_id}/, and the socket at /ws/bot/.

Required for any delivery at all. Without it a socket connection is closed with code 4001, and existing subscriptions stop firing — including ones created while the scope was still granted. Removing this scope is therefore a way to mute an app without deleting anything it owns.


What no scope grants

Some things are not on the menu, for any app, at any scope level:

  • Reading or writing another workspace's data.
  • Managing people: inviting, removing, or changing anyone's role.
  • Reading or changing billing.
  • Reading another app's token, or its subscriptions' signing secrets.
  • Anything not in the published schema. If you can reach an endpoint that is not documented, it is internal, it is not part of the promise, and it can disappear without notice.

GET bot/me/ needs no scope beyond a valid token — an app can always ask who it is and what it was granted.

When a scope is missing

A call your token has no scope for returns 403, not 404. The path exists; you were not granted it. 401 is different — that is the token being wrong, revoked, or belonging to a suspended app.

```python from juhjuh import AuthError

try: api.transition_ticket("JUJU", ticket_id, "completed") except AuthError as exc: if exc.status == 403: ... # ask an admin for tickets:write ```