Skip to content

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 + Upload can 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:

  1. Open the project, then click Access tokens in the project sidebar.
  2. Click Create token.
  3. Give it a name that's meaningful in your audit log (e.g. github-actions-acme-ios).
  4. Pick the role the token needs (see Role reference below). Read + Download + Upload is the canonical build-pipeline role and is selected by default.
  5. 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

Repo → Settings → Secrets and variables → Actions → New repository secret

Name: APPGANTRY_TOKEN. Value: the ag_prj_… token from Step 1.

Project → Pipelines → Library → Variable groups

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.

Project → Settings → CI/CD → Variables → Add variable

Key: APPGANTRY_TOKEN. Type: Variable. Tick Masked and Protect. Paste the token, save.

Step 3: call the API from CI

Every request authenticates with a single header:

Authorization: Bearer ag_prj_…

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}&notes=$(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}&notes=$(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:

  1. 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.
  2. Update the CI secret to the new value.
  3. 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