Guides

Secrets and variables

Where a workflow's secrets and variables come from on Apex, how to add them from the dashboard or the terminal, and what the job's own token can and cannot reach.

A workflow reads secrets and variables exactly as it does on GitHub: ${{ secrets.NAME }} and ${{ vars.NAME }}. What changes is where the values live. GitHub never gives a secret's value to an app, so Apex cannot copy yours. Each secret a workflow uses is added to Apex once.

Where they live

  • Per repository, or once for an organization. A repository's own secret or variable reaches that repository. An organization's reaches all of its repositories, only its private ones, or the ones you select, as on GitHub. When both define the same name, the repository's value wins.
  • Write-only. Once a secret is saved, nobody can read it back, you included. Its name is listed and its value is never shown. Replacing it means writing a new value.
  • Trusted runs only. A pull request from a fork receives no secrets, and a run Dependabot started receives only Dependabot secrets, as on GitHub.

Non-secret settings go under Variables. Unlike secrets, variables can be read back, so never store a credential in one.

From the dashboard

Open the repository in the Apex dashboard and add each value on its Settings tab, under Secrets & variables. Adding a secret needs the Owner or Account Admin role in your Apex Account (Access and roles). An organization's own secrets and variables are under Organization › Secrets & variables (Owner or Account Admin), or set from the terminal or the API.

The same Settings tab lists the repository's caches, with the quota and the idle days they are evicted by. Anyone with write access can delete one.

Dependabot secrets

A run Dependabot started on pull_request, push and the other events GitHub restricts gets no Actions secrets, and a read-only token unless permissions: asks for more, as on GitHub. Store the secrets those runs need, such as a private registry's token, under Dependabot secrets on the repository's Settings tab, or with apex secret set NAME --app dependabot --repo acme/web (or --org acme). They are handed to a run Dependabot started and to no other.

From the terminal

Install the CLI with npm install -g @apex-actions/cli (Node 24 or later). It sets a secret in one step, and helps when one value belongs on many repositories:

apex login                                     # opens your browser to approve the CLI
apex secret set NPM_TOKEN --repo acme/web --repo acme/api < token.txt
apex secret list --repo acme/web               # names only
apex variable set NODE_VERSION --value 24 --repo acme/web

An organization's secret is set once, with --org and its GitHub login. --repos names the repositories that receive it, and --visibility private sends it to every private repository. Without either flag it reaches all of the organization's repositories. Reading or changing an organization's secrets and variables needs organization:read or organization:write. Owners and Account Admins hold both:

apex secret set NPM_TOKEN --org acme --repos web,api < token.txt
apex secret list --org acme                    # names, and which repositories each reaches
apex variable set REGION --value eu-west-1 --org acme --visibility private

Without the CLI, a token from Account → API tokens sets a secret through the API directly. Keep both the token and the value out of the command line by reading them from files:

printf 'Authorization: Bearer %s\n' "$APEX_TOKEN" > header.txt   # a shell builtin, so no process sees it
printf '{"value":"%s"}' "$VALUE" > body.json                      # the secret, never an argument
curl -sS -X PUT -H @header.txt -H 'content-type: application/json' --data @body.json \
  https://app.apexactions.com/api/v1/repos/acme/web/secrets/NPM_TOKEN   # 204 means set
rm header.txt body.json

A secret's value is read from standard input, from --from-file, or from a prompt that does not echo. It is never accepted as a command-line argument. Arguments are visible to other processes and are saved in shell history.

There are two ways for the CLI to authenticate:

  • apex login for a person at a terminal. It opens an approval page in your browser, showing the request and the Account it is for; you approve, and the CLI keeps a token in a file only you can read.
  • A token for scripts and CI, created with apex token create or under Account → API tokens. Export it as APEX_TOKEN. Tokens for scripts and CI covers creating, scoping and revoking one.

If APEX_TOKEN is set, the CLI uses it. Otherwise it uses the token apex login saved.

Either way the token does what its permissions allow: secrets:write and variables:write for a repository, and organization:write for an organization. It never does more than your role in the Apex Account allows. A token limited to a list of repositories cannot change an organization's secrets. Every token expires, and revoking one on the dashboard takes effect immediately.

The job's own token

Every job receives secrets.GITHUB_TOKEN without anything being added. It is minted for that job and reaches only the job's own repository, as on GitHub, so a workflow that clones or checks out another private repository needs a token of its own, stored as a secret. Its permissions follow permissions: (the job's block, else the workflow's). With no block it carries GitHub's permissive default, limited to what the Apex App holds: issues, statuses and deployments are not among them. A pull request from a fork gets a read-only token for its own repository and no secrets. A run Dependabot started gets a read-only token unless permissions: asks for more, as on GitHub.

A trusted job in a private repository can uses: a private action from another repository of its organisation when that repository's Actions → Access setting allows it, as on GitHub; Apex fetches it with a read-only credential for those repositories alone. Reading that setting needs the App's Administration: read, which each installation's owner approves; until then such an action fails to fetch. A called reusable workflow follows the same setting.

It cannot publish to or install from GitHub Packages (npm.pkg.github.com, ghcr.io), whatever permissions: packages asks for. GitHub Packages accepts a personal access token (classic) or GitHub Actions' own token, and nothing else, and a job's token on Apex is neither. Create a personal access token (classic) with write:packages to publish, or read:packages to install, store it as a secret, and pass it where the workflow passed GITHUB_TOKEN:

- uses: actions/setup-node@v4
  with:
    node-version: 24
    registry-url: https://npm.pkg.github.com
    scope: '@your-org'
- run: npm ci
  env:
    NODE_AUTH_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
- run: npm publish
  env:
    NODE_AUTH_TOKEN: ${{ secrets.PACKAGES_TOKEN }}

For containers, log in to ghcr.io with the same secret as the password. The compatibility matrix lists this with the rest of what does not carry over. One value can go on many repositories at once: apex secret set PACKAGES_TOKEN --org acme --repos web,api < token.txt.