API tokens for CI and automation
Give each pipeline, cron job and monitor its own VPSNine token with the smallest scope, an address limit where it fits, an expiry, and a plan for rotating it.
For automation, make one VPSNine API token per job, with read-only scope unless the job changes something, an expiry, and an address limit when the job runs from fixed addresses. Store it in your CI's secret store, never in the repository, and replace it before it expires.
This page is about choosing those settings. Create and use API tokens covers the form itself, step by step.
What you need
- A VPSNine account with a confirmed email address.
- Your account password (the token form asks for it).
- Somewhere to keep secrets: GitHub Actions secrets, GitLab CI/CD variables, a password manager, or a file only one user can read.
Pick the scope for each job
A token can do anything its scope allows, as you. Match the scope to what the job does:
| Job | Scope | Why |
|---|---|---|
| Monitoring, dashboards, an Ansible inventory | read only | It only lists servers, invoices or snapshots. |
| Deploy on push with a snapshot first | read and write | Taking a snapshot changes something. |
| Preview servers per pull request | read and write | It orders and deletes servers. |
| The vps9 CLI on your laptop | read and write | You'll use it for everything. |
There is no per-server scope yet: a read-and-write token can reboot or delete any server on the account. Keep those tokens to jobs you trust, and give everything else read-only.
On a team account, a token also never does more than its maker's role. A token made by a Technical member can't touch billing, whatever its scope, and if that person's role is lowered, their tokens follow at once. For a shared pipeline, make the token from a login whose role fits the job.
Limit where it works from
Only from these addresses takes one address or range per line, IPv4 or IPv6:
198.51.100.0/24
2001:db8:4::/48From anywhere else the API answers 403 ip_not_allowed and names the address it saw. Use it when the job runs from addresses you know: a self-hosted runner, one of your own servers, an office network. List both the IPv4 and the IPv6 range if the machine has both, because it may reach us over either.
Don't use it for GitHub-hosted runners or other shared CI machines: their addresses change from run to run and belong to the provider, so a limit either breaks the job or allows everyone on that provider. For those, rely on the secret store, a short expiry and a narrow scope instead.
Set an expiry, and rotate before it
Pick 30 days, 90 days (the default) or a year. "never" is there for the rare job you can't touch, and it's the token most likely to outlive the person who made it.
To replace a token without downtime:
- Make the new token with the same settings and a name that says which one it is, like
github-actions deploy 2027-01. - Put it in the CI secret store in place of the old one.
- Run the job once, or wait for its next run, and check that last used on the new token has a time.
- Revoke the old token.
We email you when a token is created, so a token you didn't make shows up in your inbox.
Store it as a secret
In GitHub Actions, add it under the repository's Settings > Secrets and variables > Actions as VPS9_TOKEN, and pass it to only the steps that need it:
- name: Snapshot the server first
env:
VPS9_TOKEN: ${{ secrets.VPS9_TOKEN }}
run: scripts/snapshot.sh web-1Secrets aren't given to workflows triggered from forks, which is what you want for a token that can change servers.
In GitLab CI, add a CI/CD variable called VPS9_TOKEN, masked and protected so only protected branches get it.
For a cron job on a server, keep the token in a file only that user can read, and read it at run time:
install -m 600 /dev/null ~/.vps9-token
nano ~/.vps9-token
export VPS9_TOKEN="$(cat ~/.vps9-token)"The vps9 CLI and the TypeScript SDK both read VPS9_TOKEN from the environment, so nothing else needs the token.
Check it worked
Run this from the machine that will use the token. It shows the account and your role, and fails with the error code if the scope or address limit is wrong:
curl -sS --fail-with-body -H "Authorization: Bearer $VPS9_TOKEN" https://my.vpsnine.com/api/v1/meIn the panel, last used on the token now has a time.
Troubleshooting
403 ip_not_allowed: "This token only works from 198.51.100.0/24; this request came from 203.0.113.7." The job reached us from the address in the message. If that address is yours, add it (or its range) to a new token. If it changes from run to run, the job shouldn't use an address limit.
401 unauthorized in CI but not on your machine. The secret usually has a trailing newline or quotes pasted in, or the step doesn't get the secret (a fork's pull request, or a job that doesn't set env). Print its length, never its value: echo "${#VPS9_TOKEN}" should say 57.
429 rate_limited from parallel jobs. All tokens on an account share 120 requests a minute. See Rate limits and request ids.
Doing it on VPSNine
The jobs these tokens drive run on your own servers; compare the plans for build boxes and runners. The developer hub links everything else for automation.