Skip to content

Authorization

Authentication decides who you are. Authorization decides what you can do.

AppGantry uses several overlapping checks:

  1. Role checks: is the developer a member of the org / project with the right role?
  2. Project access token role checks: if the request authenticated via a project access token, does the token's granted role cover the action, and does its bound project / org match the URL?
  3. PAT scope checks: if the request authenticated via a personal access token, does the PAT carry the right scope?
  4. PAT org-binding: does the PAT's bound org match the URL's org?

A request that fails any check returns HTTP 403 with error: "permission_denied" or error: "insufficient_role".

Organization roles

Members of an organization carry one or more of these roles:

Role What it lets the developer do
READ View org metadata, member list, projects.
EDIT_PROJECT Create / edit / delete projects.
MANAGER All project-level operations across every project in the org.
BILLING View invoices, set spend caps, manage payment methods.
EDIT_ORGANIZATION Edit org name, retention defaults, members.
ADMIN Superset of every other role.

Roles compose: a single member can hold any combination. ADMIN implies every other role.

Project roles

Projects layer additional, finer roles on top of org membership:

Role What it lets the developer do
READ View project metadata + builds.
DOWNLOAD_BUILD Pull a build artifact (gated by license / billing).
UPLOAD_BUILD Upload new builds (also gated by billing on Team).
EDIT_PROJECT Edit project name, settings.
MANAGE_TESTERS Invite, remove, regroup testers.
MANAGE_RELEASES Create and update releases / channels.
DELETE_BUILD Permanently delete a build (audit-logged).
ADMIN Superset of every project role.

Org-MANAGER fallback

A developer with OrganizationMemberRole.MANAGER on a project's owning org is treated as having the full project role set without needing a per-project membership row. Project-level grants are checked first; org-MANAGER is the fallback.

This makes "an org admin can operate on every project without per-project memberships" work without requiring you to keep per-project memberships in sync for admins.

Project access tokens

A project access token authorizes by project role, not by scopes. At creation it is granted a role from the project roles above, capped at the creating developer's own effective role in the project. The token can never do more than that role allows, and never more than the developer who minted it could do.

Granted role Typical use
Read Read-only automation: list builds, read metadata.
Read + Download Mirror or archive build artifacts.
Read + Download + Upload The canonical CI build pipeline.
Maintainer Automation that also manages channels or testers.
Admin Full control of the project's build and release surface.

A project access token is bound to one project and its organization when created. A request whose ?organization_id= / ?project_id= doesn't match the token's binding is rejected, so a leaked token is confined to a single project.

Why bind to one project?

A project access token has no human owner, so it cannot inherit a developer's roles. Binding it to one project with an explicit, capped role keeps a leak confined to that project rather than everything its creator can reach. If your CI pushes to several projects, create one token per project.

Managing project access tokens (create, rotate, revoke) is interactive-only: those routes require a logged-in developer session and reject both PATs and project access tokens. So even an Admin project access token cannot mint or revoke other tokens.

PAT scopes

Personal access tokens carry their own scope bitmask. Scopes are an intersection with the underlying developer's roles: if either says "no", the request is denied.

Scope Lets the token…
builds:write Upload builds, complete uploads, set notes.
builds:read List builds and read metadata.
testers:write Invite and remove testers.
testers:read List testers.
releases:write Create and update releases / channels.
releases:read List releases / channels.
audit:read Read the org's audit feed.
pat:revoke Revoke other PATs (used by ops tooling).

A PAT is also bound to one organization when you create it. A request whose ?organization_id= query parameter doesn't match the PAT's bound org is rejected, even if the developer who owns the PAT is a member of both orgs.

Why bind PATs to one org?

Leaking a PAT is bad. Leaking a PAT that grants access to every org you belong to is much worse. Per-org binding limits the blast radius to a single tenant.

If you need to script against multiple orgs from a single developer account, create one PAT per org.

Authorization for testers

Testers don't hold roles or scopes. Their authorization is purely the invite token they were given. That token is the credential; the identity, and the authorization scope, all at once.

An invite token authorizes:

  • Loading the install page for a specific build (or set of builds the tester was invited to).
  • Downloading the build artifact through a signed URL.

…and nothing else. Testers cannot read your audit feed, see your members list, or list other testers.

Common 403 shapes

When a request fails authorization, the error code tells you which check failed:

error Cause
permission_denied The identity has no membership / scope that grants this action.
insufficient_role The identity is a member, but lacks the specific role / scope this action requires.
email_not_verified The developer hasn't verified their email yet.
operation_not_allowed The credential type is wrong (e.g. a PAT was used on a route that requires a JWT).

All four return HTTP 403.

See also