Skip to content

Authentication

Every request to AppGantry carries one of a small set of credentials. This page covers what each one is, how long it lives, and how to look after it.

Credential types

Credential For Carried as
Access token Developers, in the web app or in a programmatic client Authorization: Bearer <jwt>, or a browser cookie
Refresh token Renewing an access token Request body, or a path-scoped browser cookie
Personal access token An individual's scripts and tooling Authorization: Bearer ag_pat_…
Project access token CI and automation Authorization: Bearer ag_prj_…
SDK app secret An install enrolling with the in-app updates SDK Sent once, at enrollment
SDK update token An enrolled install checking for updates Held by the SDK
Invite token Accepting a member invitation, or a channel tester invitation In the invitation link
Enrollment token A device being registered In the enrollment link

Lifetimes, in one table

Thing Lifetime
Access token 10 minutes
Refresh token 30 days, single-use
Personal access token Until revoked, or an expiry you set
Project access token Until revoked, or an expiry you set
Pending build upload URL 1 hour
Over-the-air install capability 15 minutes
Device enrollment link 1 hour
Channel tester invitation 10 minutes
Organization or project member invitation 14 days
SDK update token 30 days

A pending organization or project tester grant is not in that table on purpose: it has no expiry at all and waits until the person signs up with that address and verifies it. See Managing testers & groups.

Password reset and email verification links are also time-limited. The email tells you what to expect. Asking for a fresh one carries no charge, but it is not delivery on demand: a second email to the same recipient inside the per-recipient cooldown below is absorbed silently and nothing new is sent, so the link already in the inbox stays the live one. Treat them as single-use and short-lived rather than memorising a number.

A data-export download link is time-limited too, but it is not in that group: generating a fresh export is capped, and an archive can only be downloaded so many times. The limits and the refusals they produce are in Account & data → How often you can do this.

Re-sending is throttled rather than unlimited, and the two families of resend do not answer a too-soon request the same way.

  • A transactional email — signup verification, password reset, or the verification for an email change — is capped at roughly one of each kind per recipient every 5 minutes, each on a cooldown of its own, and a request inside that window is absorbed silently: you get the same success the first request got, and nothing is sent. Nothing tells you it was too soon, deliberately. See Email verification for what that looks like, and Anti-enumeration for why.
  • Re-sending a pending member invitation, or re-notifying a pending organization or project tester, is capped at one send every 5 minutes — counting the original invitation, and the notification an added tester gets — and is refused out loud. Too soon answers 429; an identifier with nothing pending behind it answers 404, whether it never existed, was already accepted, revoked or cancelled, or — for an invitation, which is the only one of the two that expires at all — lapsed and was swept. See Errors → 429 for both bodies, and Rate limits for the limits in one table.

Do not carry the email behaviour over to the other two: a resend that answers 429 has told you something, and a client that treats it as a success re-sends into a closed window forever.

Signing in

A developer signs in with an email address and password. Depending on the account, sign-in can then require a second factor — a TOTP code, a recovery code, or a passkey — before any tokens are issued. Accounts in an organization with SAML SSO configured sign in through their identity provider instead.

A successful sign-in yields an access token and a refresh token. Programmatic clients get them in the response body; browsers get them as HttpOnly, SameSite=Lax cookies (Secure outside local development), with the refresh cookie path-scoped to the login routes so it isn't sent on every request.

Passkeys can also be used instead of a password, as a complete sign-in.

Access tokens

Property Value
Type Signed JWT
Lifetime 10 minutes
Claims Subject, email, issuer, audience, issued-at, expiry, and a session identifier

A token missing a required claim is rejected outright rather than defaulted. Ten minutes is deliberately short: it bounds the damage from a leaked token without making you re-authenticate, because refresh is automatic.

Don't inspect or depend on the claim set. Treat the access token as opaque; the only supported operations are "send it" and "replace it when it expires".

Refresh tokens

Refresh tokens last 30 days and are single-use. Exchanging one gives you a new access token and a new refresh token; the one you used is immediately dead.

Reuse revokes the whole session family

If a refresh token is presented twice, AppGantry treats it as evidence of theft and revokes the entire chain of tokens descended from that sign-in — not just the token you replayed. The legitimate client and the attacker are both signed out, and the developer has to sign in again.

This is the correct behaviour, and it means your client must store the new refresh token before using the new access token. A client that races two refreshes, or that retries a refresh after a timeout without checking whether the first one succeeded, will log its user out.

Practical rules for a well-behaved client:

  • Serialise refreshes. One in flight at a time, per session.
  • Persist the new refresh token atomically, before anything else.
  • On a refresh failure, sign in again. Don't retry the same token.

Sessions

Every sign-in creates a session, and a developer can see all of theirs — device, browser, approximate location, last-used time, and which one is current — and act on them:

  • Revoke one session. That device can no longer refresh. Its current access token stops working within its short remaining lifetime.
  • Log out everywhere. Revokes every session, including the one you're using, and invalidates outstanding access tokens fleet-wide. This is the button to press if you think an account is compromised, after changing the password.

Ordinary sign-out signs out that session, not every device you own. Other devices you are signed in on stay signed in.

Session management requires an interactive session; personal access tokens can't list or revoke sessions.

Multi-factor authentication

  • TOTP from any authenticator app. Enrollment shows a QR code and requires a valid code to confirm, so a half-configured authenticator can't lock you out.
  • Recovery codes, issued when you enroll. Single-use, shown once, and regenerable — regenerating invalidates the previous set. Store them outside the device holding your authenticator.
  • Passkeys, which can act as a second factor or replace the password entirely. Up to 20 per developer, each nameable and individually removable.

An organization can require multi-factor authentication of its members, optionally with a grace period, and can choose whether a passkey satisfies the requirement. Members without a factor get HTTP 403 mfa_enrollment_required once the grace period is over, which the web app turns into an enrollment prompt.

See MFA, passkeys & sessions.

Personal access tokens

A personal access token acts as you, bound to one organization, with a chosen set of scopes.

Property Value
Prefix ag_pat_
Bound to One developer + one organization
Lifetime Until revoked, or an expiry you choose
Shown Once, at creation

Scopes are capped by your own permissions: you can't mint a token that can do more than you can. Tokens can be rotated (new secret, same grants) and revoked. Revoked tokens stay listed for audit purposes and can then be permanently deleted.

Scopes

Pick the smallest set that does the job. Scopes are a bitmask: sum the values of the scopes you want and send that integer as scopes. A CI token that uploads a build and promotes it needs read + upload_build + manage_releases, which is 1 + 2 + 8 = 11.

Scope Value Grants
read 1 Read-only: list and read builds, channels, releases and testers. No mutation.
upload_build 2 Upload a new build — the common CI scope
revoke_build 4 Revoke (soft-delete) a build
manage_releases 8 Create and roll back releases, promoting builds between channels
manage_testers 16 Mint and revoke channel tester invitations. Listing them is an ordinary read-scope call. Adding or removing a tester grant, re-notifying a pending one, and tester groups need ALL — see Authorization → PAT scopes
edit_project 32 Manage project structure: create, edit and delete channels. Usually a one-time setup task, not something CI needs.

The web app renders one checkbox per scope, so you never handle the bitmask there.

The ALL scope is the one value you cannot work out from that table. On the wire it is the single integer 9223372036854775807, and it is not the sum of the six named bits: 1 + 2 + 4 + 8 + 16 + 32 is 63, which is only the six scopes that exist today, while ALL is a sentinel meaning every present and future scope, so a token minted with it also carries any scope added later.

Send it as the whole scopes value rather than combining it with anything:

{
  "name": "plan-change (temporary)",
  "scopes": 9223372036854775807
}

That body is POST /api/v1/pats?organization_id=…, from an interactive sign-in — the web app's create form offers one checkbox per named scope and cannot mint this one. The exact request and response fields are in the interactive API reference, and which operations demand ALL (and who may grant it) is in Authorization → The ALL scope.

Ask the API which scopes you may grant before offering them: GET /api/v1/pats/grantable-scopes returns your ceiling as both a bitmask and a per-scope boolean map, which is how the create screen shows only the scopes you can actually pick instead of letting you choose one that would be refused with 403.

Some operations are refused whatever scope a token carries — see Where long-lived tokens are refused.

A developer manages their own tokens from Account settings. An organization Admin can additionally list and revoke every PAT in their organization, which is the incident-response and leaver path; there is no screen for it, so it is driven over the API. See Offboarding a member.

Removing someone from an organization withdraws their tokens' authorization there, but it does not delete the tokens or touch the ones they hold in other organizations. Revoke rather than relying on removal.

For CI, prefer a project access token — it isn't tied to a person who might leave.

Project access tokens

A project access token belongs to the project, not to a developer, so it survives personnel changes.

Property Value
Prefix ag_prj_
Bound to One project + its organization
Grants A project role, capped at the creator's own role
Lifetime Until revoked, or an expiry you choose
Shown Once, at creation

See CI uploads.

Where long-lived tokens are refused

Some surfaces require an interactive sign-in and reject personal and project access tokens outright, whatever their scope. The schema marks every one of these operations, and each declares 403:

Surface Covers
Account settings Reading, editing, or deleting your own developer record; the avatar; starting an email change and its passkey step-up
Account security MFA status, TOTP enrollment and confirmation, recovery-code regeneration, disabling MFA, every passkey operation, and listing or revoking sessions
Token management Creating, listing, rotating, revoking, and permanently deleting personal and project access tokens, and reading your grantable scopes. A token cannot mint or manage tokens
Organization creation POST /api/v1/organizations
SSO configuration Reading, creating, replacing, or deleting the SAML connection; enabling it; enforcing require-SSO; claiming, listing, and verifying domains; the SP metadata and the signed-in logout read
Storage configuration (BYOSA) Reading the storage backend, submitting or withdrawing an onboarding request, and running diagnostics
Invitation acceptance Accepting an organization or project invitation, and accepting a channel tester invitation
Your personal audit feed GET /api/v1/audit/developer/{developer_id}, which is self-only

Three SSO endpoints are unauthenticated, and a fourth that shares their name is not

The SSO sign-in redirect, the assertion-consumer callback, and the Single Logout service the identity provider calls back on carry no AppGantry credential at all — the browser or IdP that reaches them has not signed in here, and on the SLO endpoint the SAML signature is the credential. Refusing a token there would be meaningless.

Beginning SP-initiated Single Logout is a different operation: the web app calls it while signed in, so it is on the interactive-only list above alongside everything else a member does to configure or use SSO.

Sign in interactively for those operations. See Authorization.

Email verification

New accounts must verify their email address before doing anything state-changing. Until then, state-changing calls return HTTP 403 with error: "email_not_verified"; reads keep working. The verification link is time-limited and single-use.

You can request a new verification email, but there is a per-recipient cooldown of about 5 minutes: a second request inside that window is silently absorbed rather than refused. The response looks exactly like a successful one, because saying "too soon" would tell an attacker that the address is on file — see Anti-enumeration.

That is safe to sit through, because the link already in the inbox stays valid. If a resend seems not to have arrived, use the previous email rather than pressing the button again.

The same 5 minutes governs password-reset emails and the verification for an email change, but each has a cooldown of its own: a send of one kind never absorbs another, so a change requested inside the signup window still goes out.

Password reset

Requesting a reset always succeeds from the caller's point of view, whether or not the address is registered. If it is registered, an email goes out with a short-lived, single-use link.

Anti-enumeration

Sign-in, password reset, email verification, and invitation acceptance deliberately return the same response whether or not the address exists. That's a trade-off: someone who mistypes their address sees the same cheerful message an attacker would. It removes the oracle attackers use to build target lists, which is worth more.

The web app mirrors this, so don't read anything into a success message.

Tester and device tokens

Testers do sign in — a tester is a developer account with tester grants — but they never manage anything. What they use is:

  • a channel invitation link, time-limited and single-use, that attaches that channel's grant when they open it and accept; and
  • an install capability, minted per install and valid for about 15 minutes, which is what actually authorizes the download.

Only the channel level issues one. An organization or project tester grant carries no token and no link, so there is nothing for the tester to accept and no accept step to complete: it attaches to an AppGantry account holding that address the moment it is granted, verified or not, and otherwise waits for somebody to sign up with the address and verify it. See Managing testers & groups.

Devices being registered for Apple ad-hoc distribution use a separate enrollment link, valid for 1 hour, which collects the device identifier and hands it back to AppGantry. See Enrolling test devices.

SDK credentials

An SDK app has:

  • an app key, a public identifier that is safe to ship inside your binary; and
  • an app secret, shown once at creation, used only to enroll an install.

An enrolled install holds a update token valid for 30 days, refreshed transparently by the SDK. Revoking the SDK app immediately stops every install using it.

Looking after credentials

  1. Never commit a token. Use your CI provider's secret store.
  2. Scope down. Give a token the least role that lets the job pass.
  3. Prefer project access tokens for automation and personal access tokens for a human's own scripts.
  4. Rotate on a schedule and immediately on suspicion.
  5. Set expiries where you can. An expiring token is one you can't forget about.
  6. Never log an Authorization header.
  7. If a credential leaks, revoke it first and investigate second.

See also