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/webAn 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 privateWithout 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.jsonA 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 loginfor 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 createor under Account → API tokens. Export it asAPEX_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.