Skip to main content

API tokens and deploying from CI

CommyPanel has a small HTTP API for scripts and CI: list apps, start a deployment, follow it. It uses bearer tokens, never your password or a browser session.

Create a token​

Settings → API tokens → New token (needs the tokens.manage permission). Choose:

  • What it may do: read apps and deployments (apps:read), start deployments (apps:deploy). You can only give what you hold yourself.
  • Which apps: all, or only the ones you tick. A token restricted to some apps gets a 404 for any other.
  • Valid for: 30 days, 90 days (the default), 6 months, 1 year, or Never expires. A token that never expires works until you revoke it or its owner is deleted (and stops if its owner loses the permissions it uses), so treat it like a password and rotate it regularly: make a new token, switch your pipeline over, then revoke the old one. The form says so when you choose it.

The token (it starts with cpt_) is shown once. Only a hash is stored. Creating and revoking ask for your password and are written to the audit log, as is every deployment a token starts.

A token is checked against its owner's current permissions on every call: if the owner loses apps.deploy, the token stops being able to deploy. Deleting the owner deletes their tokens.

Calls​

All calls are under /api/v1, send Accept: application/json and Authorization: Bearer <token>.

CallAbilityAnswer
GET /appsapps:readthe apps the token may see
GET /apps/{slug}apps:readone app: status, stack, domains, live and latest deployment
POST /apps/{slug}/deploymentsapps:deploy202 and the new deployment
GET /apps/{slug}/deployments/{number}apps:readthe deployment; add ?log=1 for the build log

Answers hold no secrets: no environment variables, keys or webhook secrets.

A deployment has status, stage, finished and succeeded. Poll it until finished is true, then check succeeded. Starting a deployment while one runs answers 409 with the running one; a stopped app answers 409 too.

BASE=https://panel.example.com
TOKEN=cpt_...

# start
curl -sS -X POST "$BASE/api/v1/apps/shop/deployments" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json"

# follow
curl -sS "$BASE/api/v1/apps/shop/deployments/12?log=1" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json"

A deployment can also have the status rollout (a gradual rollout is proving it, finished is false) or rolled_back (it went live and was rolled back by itself, succeeded is false). The watch runs after the release went live, so a job that stops at the first succeeded: true can miss a rollback minutes later.

GitHub Actions​

Store the token as a repository secret (COMMYPANEL_TOKEN) and the panel address as a variable.

- name: Deploy
env:
BASE: ${{ vars.COMMYPANEL_URL }}
TOKEN: ${{ secrets.COMMYPANEL_TOKEN }}
run: |
n=$(curl -fsS -X POST "$BASE/api/v1/apps/shop/deployments" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" | jq -r .data.number)
while :; do
d=$(curl -fsS "$BASE/api/v1/apps/shop/deployments/$n" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json")
[ "$(echo "$d" | jq -r .data.finished)" = true ] && break
sleep 5
done
[ "$(echo "$d" | jq -r .data.succeeded)" = true ]

Use a token restricted to that one app, with only apps:deploy and apps:read.

Limits​

  • 60 calls a minute per token.
  • Calls without a valid token get 20 a minute per address, and failed attempts are counted before the token is looked up, so guessing is slow from the first try.
  • No CORS: browsers on other sites cannot call it. Keep tokens out of front-end code.

Revoke a token on the same page. It stops working at once.