# API quickstart

> Your first Rack4 API calls in curl, JavaScript, Python, the TypeScript SDK and the rack4 CLI, side by side: list servers, reboot one, read errors.

Source: https://rack4.io/help/api-quickstart · Updated: 2026-11-01

To call the Rack4 API, send a request to `https://my.rack4.io/api/v1` with your [API token](https://rack4.io/help/api-tokens) in an `Authorization: Bearer` header; answers are JSON. Below, the same calls are shown in curl, JavaScript, Python, the TypeScript SDK and the rack4 CLI, so you can start in whichever you use.

The API covers your account, SSH keys, servers, firewalls, snapshots, reverse DNS, invoices, webhooks and more. The [full reference](https://my.rack4.io/api) lists every endpoint with an example in each language and a **Try it** button, and the [rack4 CLI](https://rack4.io/help/install-the-rack4-cli) uses this same API.

[Screenshot: An endpoint in the API reference: List servers, with tabs for curl, JavaScript, Python, TypeScript and the rack4 CLI, and the example answer] Every endpoint in the reference has these tabs. Your choice of language sticks as you move between endpoints.

## What you need

- An API token, made under **tokens** in the panel.
- One of: `curl` (with `jq` to make the output readable: `apt install -y jq`), Node.js 18 or later, Python 3 with `requests`, or the [rack4 CLI](https://rack4.io/help/install-the-rack4-cli).

## Set up

Make a token under **tokens** in the panel. **read only** can make every `GET` request. Anything that changes something, like adding a key or rebooting a server, needs **read and write**.

Read the token from a file so it stays out of your shell history ([Create and use API tokens](https://rack4.io/help/api-tokens) shows how to store it):

```bash
export RACK4_TOKEN="$(cat ~/.rack4-token)"
API=https://my.rack4.io/api/v1
```

Then ask who you are:

```bash
curl -s -H "Authorization: Bearer $RACK4_TOKEN" "$API/me"
```

```json
{"id":"usr_…","email":"you@example.com","emailVerified":true,"createdAt":"2026-10-09T12:00:00.000Z"}
```

If that comes back, your token works.

## The same calls in five tools

List your servers. The JavaScript and Python versions read the token from `RACK4_TOKEN` and stop with the API's error code, message and request id if something is wrong:

```bash
curl -s -H "Authorization: Bearer $RACK4_TOKEN" "$API/servers" | jq -r '.data[] | "\(.hostname) \(.state) \(.ipv4)"'
```

```js
// servers.mjs: node servers.mjs (Node 18 or later; fetch is built in)
const API = process.env.RACK4_API_URL ?? "https://my.rack4.io/api/v1";
const headers = { Authorization: `Bearer ${process.env.RACK4_TOKEN}` };

const res = await fetch(`${API}/servers`, { headers });
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.error.requestId})`);
for (const s of body.data) console.log(s.hostname, s.state, s.ipv4);
```

```python
# servers.py: python3 servers.py (pip install requests)
import os
import requests

API = os.environ.get("RACK4_API_URL", "https://my.rack4.io/api/v1")
headers = {"Authorization": f"Bearer {os.environ['RACK4_TOKEN']}"}

res = requests.get(f"{API}/servers", headers=headers, timeout=30)
body = res.json()
if not res.ok:
    raise SystemExit(f"{body['error']['code']}: {body['error']['message']} ({body['error']['requestId']})")
for s in body["data"]:
    print(s["hostname"], s["state"], s["ipv4"])
```

```ts
// With the TypeScript SDK (not on npm yet; see the SDK tutorial)
import { Vps9 } from "@vpsnine/sdk";

const rack4 = new Vps9(); // reads RACK4_TOKEN
for (const s of (await vps9.listServers()).data) console.log(s.hostname, s.state, s.ipv4);
```

```bash
rack4 servers
```

Reboot one. It answers `202` at once with `pendingAction` set to `reboot`:

```bash
curl -s -X POST -H "Authorization: Bearer $RACK4_TOKEN" -H "Content-Type: application/json" \
  -d '{"action":"reboot"}' "$API/servers/srv_…/actions"
```

```js
const res = await fetch(`${API}/servers/srv_…/actions`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.RACK4_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ action: "reboot" }),
});
console.log(res.status, (await res.json()).pendingAction);
```

```python
res = requests.post(
    f"{API}/servers/srv_…/actions",
    headers={"Authorization": f"Bearer {os.environ['RACK4_TOKEN']}"},
    json={"action": "reboot"},
    timeout=30,
)
print(res.status_code, res.json()["pendingAction"])
```

```ts
await vps9.serverAction("srv_…", { action: "reboot" });
```

```bash
rack4 servers reboot web-1
```

We ran each of these against a local copy of the API (Node 18 and 22, Python 3.12, curl 8.5 with jq 1.7, and the rack4 CLI built from the same source) before publishing them. `requests` sets `Content-Type: application/json` for you when you pass `json=`; with `fetch` and `curl` you set it yourself, or the API answers `415`.

## SSH keys

List them:

```bash
curl -s -H "Authorization: Bearer $RACK4_TOKEN" "$API/ssh-keys"
```

You get an object with a `data` list. Each key has an `id`, `name`, `type`, `fingerprint`, `publicKey` and `createdAt`. With `jq`, just the names and fingerprints:

```bash
curl -s -H "Authorization: Bearer $RACK4_TOKEN" "$API/ssh-keys" | jq -r '.data[] | "\(.name)  \(.fingerprint)"'
```

Add one. Let `jq` build the JSON so the key text gets quoted properly:

```bash
jq -n --arg name laptop --arg key "$(cat ~/.ssh/id_ed25519.pub)" '{name: $name, publicKey: $key}' |
  curl -s -X POST -H "Authorization: Bearer $RACK4_TOKEN" -H "Content-Type: application/json" --data @- "$API/ssh-keys"
```

A new key comes back with status `201` and its `id`. The rules match the panel: Ed25519, ECDSA, or RSA of 2048 bits or more, one key per request, 50 at most. `name` is optional.

Delete one by its `id`:

```bash
curl -s -X DELETE -H "Authorization: Bearer $RACK4_TOKEN" "$API/ssh-keys/key_…"
```

That returns `204` and no body.

## Servers

List them, showing the id, hostname and state:

```bash
curl -s -H "Authorization: Bearer $RACK4_TOKEN" "$API/servers" | jq -r '.data[] | "\(.id)  \(.hostname)  \(.state)"'
```

Reboot one by its `id`:

```bash
curl -s -X POST -H "Authorization: Bearer $RACK4_TOKEN" -H "Content-Type: application/json" \
  -d '{"action":"reboot"}' "$API/servers/srv_…/actions"
```

It answers `202` straight away, with the server's `pendingAction` set to `reboot`. The reboot itself happens in the background, so fetch the server again to see when it's done. `start`, `stop`, `reinstall` and `delete` work the same way. The API doesn't ask you to confirm a `reinstall` or `delete`, so check the id first.

`POST /servers` places an order. It answers with an invoice, and the server is built once the invoice is paid; if your account credit covers it, that happens straight away. Send an `Idempotency-Key` header with a value you choose, a UUID for example, and keep it until you get an answer. If the request times out, send it again with the same key, and you get the order you already placed instead of a second one.

An order can also name a [one-click app](https://rack4.io/help/one-click-apps) in `app`, a list of extra distribution packages in `packages` (20 at most) and a first-boot script in `setupScript` (16 KiB at most). All three are optional:

```json
{"plan":"standard","image":"ubuntu-24.04","hostname":"web-1","sshKeyIds":["key_…"],"app":"docker","packages":["htop"],"setupScript":"#!/bin/bash\nset -eu\n…"}
```

[Extra packages and setup scripts](https://rack4.io/help/setup-scripts) shows how to send a script file with `jq`.

## When it says no

Errors are JSON too, with a code you can match on and a message meant for people:

```json
{"error":{"code":"insufficient_scope","message":"This token does not have the write scope.","requestId":"req_7d2kq4m5x3n6p2r4s7t2v3w4"}}
```

`requestId` is also in the `X-Request-Id` header of every answer. Put it in a support ticket and we can find that exact request. [Developers](https://rack4.io/help/developers) covers request ids, rate-limit headers and safe retries.

| Status | Code | What it means |
| --- | --- | --- |
| 401 | `unauthorized` | Token missing, wrong, revoked or expired. |
| 400 | `invalid_json` | The body isn't valid JSON. |
| 403 | `insufficient_scope` | A read-only token tried to change something. |
| 403 | `email_unverified` | Ordering needs a confirmed email address. [Confirm it](https://rack4.io/help/verify-your-email) first. |
| 403 | `staff_only` | Orders are paused for now, which is rare. Nothing was ordered; try again later. |
| 404 | `not_found` | No such key or server, or no such endpoint. |
| 405 | `method_not_allowed` | The path exists, but not with that method. The `Allow` header lists the ones it takes. |
| 409 | `duplicate_key` | That key's already on your account. |
| 409 | `action_pending`, `invalid_state` | The server is busy with another action, or in a state where that action can't run. |
| 413 | `body_too_large` | Bodies are 16 KB at most, or 64 KB for `POST /servers` and server actions, which can carry a setup script. |
| 415 | `unsupported_media_type` | A body sent without `-H "Content-Type: application/json"`. Plain `curl -d` sends a form, which the API refuses. |
| 422 | `invalid_request` | The body isn't a JSON object, or has a field the endpoint doesn't know or of the wrong type (a number where text goes, say). |
| 422 | `invalid_key`, `invalid_name`, `too_many_keys` | The key or name was refused; the message says why. |
| 429 | `rate_limited` | Over 60 requests a minute on this token, or 120 across all your account's tokens. Wait the `Retry-After` seconds. |

Add `-i` to any of these `curl` commands to see the status line and headers. And don't bother trying a browser cookie: the API ignores cookies entirely. A token is the only way in.

## Next steps

- [Use curl and jq with the Rack4 API](https://rack4.io/tutorials/use-curl-and-jq-with-the-rack4-api): filtering, waiting, paging and firewall edits from a shell.
- [Script your infrastructure with the TypeScript SDK](https://rack4.io/tutorials/script-rack4-with-the-typescript-sdk).
- [Use the OpenAPI spec](https://rack4.io/help/use-the-openapi-spec) to generate a client in another language.
- [Rate limits and request ids](https://rack4.io/help/api-rate-limits-and-request-ids) and [idempotent orders](https://rack4.io/help/idempotent-orders-with-idempotency-key).
- The [plans](https://rack4.io/plans) an order can name.
