CI tokens
Organization-scoped, expiring credentials for CI pipelines and scripts. What a CI token can do, how to create, use, rotate and revoke one, and how it is kept safe.
A CI token is a credential your organization gives to automation: a GitHub Actions workflow, a GitLab pipeline, a cron job on a server. It replaces the old pattern of storing a person's refresh token in CI.
- It acts for the organization, not for the person who created it, so it keeps working when that person leaves.
- It can do exactly what its scopes allow, nothing more, whatever role its creator has.
- It always expires (1 hour to 366 days) and can be revoked at any time.
- Its secret is shown once. Norcube stores only a hash.
Create a token
In the dashboard, open IAM › CI tokens and click Create token:
- Name it after where it runs (
GitHub Actions deploy), so you know what breaks if you revoke it. Names are unique among an organization's active tokens. - Pick scopes. Grant only what the job needs. You can grant a scope only if you hold every permission it covers across the whole organization; scopes you can't grant are shown disabled.
- Pick an expiry: 30 days, 90 days, 1 year, or a date.
- Copy the token (
nrc_ci_…) and store it in your CI's secret store. It is not shown again.
Creating, listing and revoking tokens need the IAM permissions
auth:CreateCIToken, auth:ListCITokens and auth:RevokeCIToken.
Organization owners hold them; grant them to others with a
policy.
Scopes
| Scope | Allows |
|---|---|
backup.read | Read data sources, backup jobs, restore checks and policies. |
backup.write | Start a backup of a data source, plus everything backup.read allows. |
backup.verify | Run a restore check (into a throwaway database), plus everything backup.read allows. |
domainradar.check | Run single and bulk domain checks (metered) and read results. |
langsync.read | Read namespaces, terms with translations, languages and the glossary. |
langsync.sync | Run nrc langsync sync (push source terms, pull translations), plus LangSync reads. |
langsync.write | Add and edit terms, write translations, translate missing values, plus LangSync reads. |
billing.read | Read billing status, usage, estimates, invoices, credits and the subscription. |
cms.read | Read CMS sites, content files and their history, schema and media. |
cms.write | Edit and delete content files and upload or manage media, plus everything cms.read allows. |
Anything a scope doesn't list is refused with 403 INSUFFICIENT_SCOPE,
including every product's settings and administration: a CI token can't
create namespaces, change backup policies, edit payment methods or
manage members. Metered actions (checks, AI translation) count towards
your plan like any other use.
Use a token
With the Norcube CLI
Set the token as NORCUBE_TOKEN and the CLI (0.18 and later) uses it
instead of a sign-in:
# .github/workflows/translations.yml
- name: Sync translations
run: nrc langsync sync
env:
NORCUBE_TOKEN: ${{ secrets.NORCUBE_TOKEN }}Over HTTP
Exchange the token for a short-lived access token to one product, then call that product's API with it. The exchange follows OAuth 2.0 Token Exchange (RFC 8693):
curl -s https://auth.api.norcube.com/auth/ci/token \
-d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
-d subject_token_type=urn:norcube:params:oauth:token-type:ci-token \
-d subject_token="$NORCUBE_TOKEN" \
-d audience=langsync-apiYou can also send the CI token as Authorization: Bearer nrc_ci_… and
leave out subject_token. The response:
{
"access_token": "eyJ…",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 300,
"scope": "langsync.sync",
"audience": "langsync-api",
"organization_id": "62658568-5050-49fc-b331-f61bbdf850ad",
"organization_slug": "acme",
"organization_name": "Acme",
"ci_token_id": "0b7a4c8e-2d3f-4a1b-9c6d-5e4f3a2b1c0d",
"ci_token_name": "GitHub Actions deploy",
"ci_token_expires_at": "2027-01-27T09:00:00Z"
}audience | Product |
|---|---|
snapdb-api | Backup |
langsync-api | LangSync |
domainradar | DomainRadar |
billing | Billing |
cms-api | CMS |
The access token lives 5 minutes. Reuse it for that long instead of exchanging on every request: the exchange allows 600 requests a minute per IP address.
| Status | type | Meaning |
|---|---|---|
401 | INVALID_TOKEN | The token is malformed, unknown, expired or revoked. These aren't told apart. |
400 | INVALID_AUDIENCE | audience isn't one of the products above. |
403 | INSUFFICIENT_SCOPE | The token has no scope for that product. |
429 | TOO_MANY_REQUESTS | Too many exchanges from your address; wait for Retry-After. |
Rotate and revoke
- Rotate before a token expires: create a new token, update the CI secret, then revoke the old one. The list shows when each token was last used and from which address, so you can see when the old one goes quiet.
- Revoke from IAM › CI tokens. The token can't be exchanged any more at once; access tokens already issued from it stop working within 5 minutes.
- Revoked and expired tokens stay listed for 30 days, then they're deleted. Deleting the organization ends all its tokens.
Who did it
A CI token is its own identity. Actions it takes are recorded under
CI token · name (in audit logs, in the CMS commit history, as the
author of synced terms), never under the person who created it. The
creator is kept on the token for your records.
Security
- Format.
nrc_ci_+ 32 random characters + a 6-character checksum. The fixed prefix lets secret scanners (GitHub push protection, gitleaks) recognise a leaked token; the checksum catches typos and truncated copies. - Storage. Only a SHA-256 hash of the token is stored, plus its last 4 characters to tell tokens apart. Nobody at Norcube can read it back.
- No escalation. A token can never do more than its creator could do across the whole organization when they created it, and its scopes can't be changed afterwards: create a new token instead.
- Treat it like a password. Keep it in your CI's secret store, never in a repository or a log. If one leaks, revoke it.
Related
- Tokens: how access tokens, audiences and refresh work.
- Members & IAM: the permissions behind scopes.
- CLI:
NORCUBE_TOKENand the other environment variables.