Skip to content
Automating / API

API

Reading your account without a browser, and proposing changes that wait for a person.

The API reads. It does not change anything – changes go through change requests, which wait for a person.

There are no sessions, cookies or CSRF here. A token says which account, so a client never has to know the account id, and the API sits outside the account prefix.

Tokens

Account → API tokens → New, as an admin. Choose whether it may propose changes; read-only is the default and the right answer most of the time.

The token is shown once. Only its digest is stored, so a stolen database yields nothing and nobody can be shown their token a second time. Tokens are prefixed, so one found in a log or a repository is recognisable for what it is.

Revoke one any time from the same page.

Authenticating

Authorization: Bearer kc_xxxxxxxxxxxx

The older Token token="..." scheme is understood too. A request without a valid token gets a 401 saying what to send.

Endpoints

Servers

GET /api/v1/servers
GET /api/v1/servers/:id
{
  "id": 12,
  "name": "web-1",
  "provider": "hetzner",
  "status": "running",
  "api_managed": true,
  "ip": "203.0.113.10",
  "server_type": "cx42",
  "location": "nbg1",
  "cores": 8,
  "memory": 16.0,
  "disk": 160,
  "os": "Ubuntu 24.04",
  "hardening_status": "succeeded",
  "created_at": "2026-01-14T09:02:11Z"
}

api_managed is there so a caller can tell why a self-hosted machine reports no type, location or price, rather than treating the nulls as an error.

Apps

GET /api/v1/apps
GET /api/v1/apps/:id

Each app carries its repository and its environments, with branch, service and server.

Actions

GET /api/v1/actions

The catalogue of what may be proposed, with the parameters each action takes.

Change requests

GET  /api/v1/change_requests[?status=pending|approved|applied|rejected|failed]
GET  /api/v1/change_requests/:id
POST /api/v1/change_requests

POST needs a token with write access, and answers 202 Accepted:

curl -X POST https://kamalcloud.example.com/api/v1/change_requests \
  -H "Authorization: Bearer $KAMAL_CLOUD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "action_name": "environments.deploy",
        "params": { "environment_id": 7 }
      }'
{
  "change_request": {
    "id": 41,
    "action": "environments.deploy",
    "params": { "environment_id": 7 },
    "status": "pending",
    "description": "Deploy environment 7.",
    "error": null,
    "result": null,
    "requested_at": "2026-09-14T11:20:04Z",
    "decided_at": null,
    "applied_at": null,
    "decided_by": null
  }
}

Nothing has been deployed. Somebody has to approve it. Once they have, decided_at and decided_by say when and who, applied_at and result say what the work produced, and error says why it failed if it did.

Responses

Lists return at most 50 entries. ?limit= asks for fewer; there is no page or offset. Errors are JSON with an error key: 401 for a bad token, 403 for a read-only token proposing, 404 for something not in your account, 422 for a request the action would not accept – which also returns the catalogue, so a client can correct itself.

Giving a token to an agent

This is what the design is for. An LLM agent with a KamalCloud token can answer questions about your fleet all day and cannot touch it. The only thing it can do is put a clearly described proposal in front of a person.