CI with project access tokens¶
Most teams stop using the web app for build uploads after the first week. This guide shows you how to wire AppGantry into your CI pipeline with a project access token.
What's a project access token?¶
A project access token is a long-lived credential bound to a single project (and its organization), scoped to a project role, and revocable independently of any developer's password.
Project access tokens are the right credential for CI because:
- They are owned by the project, not by a person, so they keep working when a team member leaves. Nobody's account deletion or password rotation can break your pipeline.
- They carry a project role that caps what they can do. A token granted
Read + Download + Uploadcan push builds but cannot change project settings or manage other tokens. - Each token is independently revocable, so leaking one never forces a password reset or touches anyone's account.
- Their value starts with
ag_prj_, a distinct prefix from personal tokens (ag_pat_), so leaks are easy to scan for and easy to spot in logs.
What project access tokens are not for:
- BYOSA management. Setting up, diagnosing, or tearing down BYOSA requires an interactive login (a short-lived JWT). Long-lived tokens are rejected there by design.
- Personal, interactive work tied to your own account. For that, use a personal access token instead.
Step 1: create a project access token¶
In the web app:
- Open the project, then click Access tokens in the project sidebar.
- Click Create token.
- Give it a name that's meaningful in your audit log (e.g.
github-actions-acme-ios). - Pick the role the token needs (see Role reference
below).
Read + Download + Uploadis the canonical build-pipeline role and is selected by default. - Click Create. The token value is shown once: copy it immediately. We never display it again.
The token value starts with ag_prj_ so it's easy to spot in logs (and
easy for git-secrets / trufflehog to scan for).
You can only grant a role you hold yourself: the server caps the token at your own effective role in the project. A project Maintainer cannot mint an Admin token.
Step 2: store it as a CI secret¶
Name: APPGANTRY_TOKEN. Value: the ag_prj_… token from Step 1.
Add a variable APPGANTRY_TOKEN, click the lock icon to mark it
secret, paste the token value, save the group, link it to your
pipeline's variables.
Step 3: call the API from CI¶
Every request authenticates with a single header:
The token already carries its bound organization and project, so it can only act on the project it was minted for. Upload a build:
curl -fSL -X POST \
-H "Authorization: Bearer ${APPGANTRY_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @build.ipa \
"https://api.appgantry.com/api/v1/organizations/${APPGANTRY_ORG_ID}/projects/${APPGANTRY_PROJECT_ID}/builds?platform=ios&version=${BUILD_VERSION}¬es=$(jq -rsRn .<<<"$BUILD_NOTES")"
On success you get a JSON body with the build's identifier and an install URL you can paste into a notification:
{
"id": "01HX...",
"platform": "ios",
"version": "4.2.1",
"size_bytes": 87231488,
"install_url": "https://app.appgantry.com/install/01HX..."
}
End-to-end GitHub Actions example¶
name: Build and distribute
on:
push:
branches: [main]
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
# ... your iOS build steps that produce build.ipa ...
- name: Upload to AppGantry
env:
APPGANTRY_TOKEN: ${{ secrets.APPGANTRY_TOKEN }}
APPGANTRY_ORG_ID: ${{ vars.APPGANTRY_ORG_ID }}
APPGANTRY_PROJECT_ID: ${{ vars.APPGANTRY_PROJECT_ID }}
run: |
BUILD_VERSION="${GITHUB_SHA::7}"
NOTES="Build of ${GITHUB_REF_NAME} @ ${GITHUB_SHA::7}"
curl -fSL -X POST \
-H "Authorization: Bearer ${APPGANTRY_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @build.ipa \
"https://api.appgantry.com/api/v1/organizations/${APPGANTRY_ORG_ID}/projects/${APPGANTRY_PROJECT_ID}/builds?platform=ios&version=${BUILD_VERSION}¬es=$(jq -rsRn . <<<"$NOTES")"
The org and project IDs are not secrets. Store them as repository variables (not secrets) so they're easy to read while debugging.
Role reference¶
A project access token carries a project role: a bitmask that caps everything the token can do. Pick the smallest role that gets your CI job done. A leaked token is limited to whatever role you granted it.
| Role | Lets the token… |
|---|---|
Read |
List builds and read project metadata. |
Read + Download |
The above, plus download build artifacts. |
Read + Download + Upload |
The above, plus upload builds and set notes. The canonical CI role. |
Maintainer |
Manage project configuration, channels, and testers. |
Admin |
Full control of the project's build and release surface. |
A typical CI token has the Read + Download + Upload role. Reach for
Maintainer or Admin only if your pipeline also manages channels or
testers.
The role you request is always capped at your own effective role in the project, so you can never mint a token more privileged than yourself.
Rotation & revocation¶
You can rotate or revoke any token from the project's Access tokens
page in the web app. Both actions land in the org's audit feed
(auth.project_access_token_rotated / auth.project_access_token_revoked)
and any in-flight request authenticated with the old secret starts
returning HTTP 401.
For zero-downtime rotation:
- Rotate the token from the Access tokens page. The identity, name, and role stay the same; only the secret changes and the new value is shown once.
- Update the CI secret to the new value.
- Run a CI job to confirm everything still works.
Rotation keeps the same token row (and its audit history) while cycling the secret. Revocation retires the token for good. This is the same zero-downtime pattern we recommend for storage account keys, AWS access keys, and every other long-lived credential. AppGantry just keeps the surface small.
See also¶
- Authentication: login, JWT lifetimes, project and personal token formats.
- Audit & spend caps: what token creations, rotations, and revocations look like in the audit feed.
- API conventions: error shape, pagination, idempotency keys.