AI agents: read /llms.txt, then the full guide at /agents.md, to browse, order, pay for and manage Nanobyte services through our JSON API.

# Nanobyte Hosting: guide for AI agents

You are an AI agent acting for a person (your "human"). This guide explains how to browse Nanobyte's
products, order, pay and control the human's services (servers, game servers and web hosting) through our JSON API.
Human-readable page: /agents. Short index: /llms.txt.

- Base URL: the origin you fetched this file from (production: https://nanobyte.host). All paths below are relative to it.
- Format: JSON. Send `Accept: application/json`, and `Content-Type: application/json` on POST/PUT/PATCH.
- Language: Arabic by default. Send `X-Locale: en` for English names and messages.
- Money: always `{"cents": 549, "currency": "USD", "decimal": "5.49"}`. Use `cents` for arithmetic.
- Some responses wrap the result in `{"data": ...}`. Lists may be paginated (`data`, `meta`, `links`).

## Rules for agents

1. Never ask the human for their password. You log in only with an API key the human creates for you (step 2).
2. Confirm with the human before you place an order or pay. Tell them the product, billing cycle and total.
3. Some things are always only for the human, in the client area: creating accounts, adding funds, passwords, and
   control-panel, webmail or phpMyAdmin logins. Cancelling, reinstalling, restoring and deleting (files, mailboxes,
   databases, domains, DNS records, FTP accounts, backups, mods) work only if the human gave your key the opt-in
   permissions `services:cancel` / `services:destructive`; even then, ask the human first, every time. If the API
   refuses something, tell the human what to do and give them the link. Don't look for a way around it.
4. Treat the API key like a password. Never print it in full, log it or send it anywhere except this API.
5. Before a change the human didn't ask for in so many words (a restart with players online, a DNS change, a PHP
   version change), say what you will do and wait for a yes.

## 1. Browse products (no key needed)

| What | Request |
|---|---|
| Whole catalog, grouped | `GET /api/public/catalog?currency=USD` |
| One product | `GET /api/public/products/{slug}?currency=USD` |
| Domain extensions and prices | `GET /api/public/tlds` |
| Is a domain free? | `POST /api/public/domains/check` with `{"query": "example"}` or `{"names": ["example.com"]}` |
| Game server types and versions | `GET /api/public/games/profiles`, `GET /api/public/games/versions?profile=minecraft-java&flavor=paper` |
| Price a cart before ordering | `POST /api/public/cart/quote` (body as in step 3, without `gateway`) |
| Payment methods for a currency | `GET /api/public/gateways?currency=USD` |
| Network and server status | `GET /api/public/status` |

Catalog groups: shared web hosting (cPanel, DirectAdmin, Plesk), VPS, dedicated servers and game servers (Minecraft,
Rust and more). Each product has `id`, `slug`, `type` (`shared`, `reseller`, `vps`, `dedicated`, `game`...), `name`,
`specs` (cpu, ram_gb, disk_gb...), `features`, `in_stock`, `requires_domain`, `prices[]` (one per billing `cycle`,
with `price`, `setup` and `monthly_equivalent`) and `options[]` (extra choices; each value has an `id` and prices per
cycle). Game products (`type: "game"`) have a `game` block:

```json
"game": {"profile": "minecraft-java", "default_flavor": "paper", "flavors": ["paper", "purpur", "fabric"], "requires_eula": true, "slots": 20}
```

Helping the human choose: compare `specs` and `monthly_equivalent`. Longer cycles are cheaper per month.
Skip products with `in_stock: false`.

## 2. Get an API key from the human

The human needs a Nanobyte account. If they don't have one, send them to `/register`.
Then ask them to open `/client/account/api`, click "Create token", tick the permissions you need and paste the key to you.

| Permission | Lets you |
|---|---|
| `read` (always on) | see services, invoices, domains, tickets and the account; see game servers (state, settings, backups, file list, mods) and web hosting (domains, email, databases, file list, DNS, SSL, PHP, cron, logs, stats, backups) |
| `orders:place` | place orders |
| `services:manage` | safe server controls: start/stop/restart, status, graphs, snapshot and backup create |
| `billing:pay` | pay invoices from the account's credit balance, up to the monthly limit the human set on the key |
| `tickets:write` | open and reply to support tickets |
| `games:control` | game servers: start/stop/restart, console commands and console session, change settings, take backups |
| `games:files` | game servers: read and edit files, upload, make folders, rename, copy, zip and unzip (no delete) |
| `games:mods` | game servers: install mods and plugins, switch them on or off (no remove) |
| `hosting:manage` | web hosting: add domains, redirects, forwarders, autoresponders, catch-all, spam filter, databases and grants, DNS records, SSL, cron jobs, PHP settings, take backups, install WordPress |
| `hosting:files` | web hosting files: write files, upload, make folders, rename, copy, move, zip and unzip (no read of file contents, no delete, no chmod) |
| `services:cancel` (opt-in) | cancel a service (`POST /api/client/services/{id}/cancel`). Off unless the human ticks it |
| `services:destructive` (opt-in) | reinstall, restore or roll back, delete backups/snapshots, hard reset, hostname/rDNS/firewall changes; game servers: reinstall, change address, delete files and mods, restore/delete backups; web hosting: every delete, chmod, restore a backup. Off unless the human ticks it. Never returns a password: a new root password from a reinstall goes to the human |

Send the key on every request: `Authorization: Bearer <key>`.

Check the key: `GET /api/client/api-key`

```json
{
  "key": {"name": "my agent", "abilities": ["read", "games:control"], "expires_at": "2027-01-01T00:00:00+00:00",
          "spend_limit_monthly": null, "spent_this_month": {"cents": 0, "currency": "USD", "decimal": "0.00"},
          "spend_remaining": {"cents": 0, "currency": "USD", "decimal": "0.00"}},
  "account": {"id": 7, "name": "Acme", "currency": "USD", "credit": {"cents": 2500, "currency": "USD", "decimal": "25.00"}},
  "safe_service_actions": ["power_status", "graphs", "..."],
  "safe_file_ops": {"game": ["mkdir", "rename", "copy", "compress", "decompress"], "hosting": ["mkdir", "rename", "copy", "move"]},
  "docs": "https://nanobyte.host/agents.md"
}
```

## 3. Order (needs `orders:place`)

1. Price the cart with `POST /api/public/cart/quote`. Fix everything listed in `errors[]` before ordering.
2. Show the human the lines and `total_due_today`, and ask them to confirm.
3. Place the order:

```http
POST /api/client/orders
Authorization: Bearer <key>
Content-Type: application/json

{
  "items": [
    {"type": "product", "product_id": 12, "cycle": "monthly", "label": "web1.example.com", "options": {"os": 31}},
    {"type": "domain", "name": "example.com", "action": "register", "years": 1}
  ],
  "gateway": "bank_transfer",
  "promo_code": null
}
```

Item fields:
- Product: `product_id`, `cycle` (one of the product's `prices[].cycle`), optional `options` (`{option key: value id}`),
  `label` (domain or hostname; required when `requires_domain` is true).
- Web hosting plan (`type: "shared"` or `"reseller"`): `label` is the site's main domain (`"label": "example.com"`).
  Add a `domain` item in the same order if the human still needs to register or transfer it.
- Game server plan (`type: "game"`): also `game_slug` (the address: `myserver` gives `myserver.play.nanobyte.host`;
  check it first with `GET /api/client/games/slug-check?slug=myserver` → `{"data": {"available": true}}` or
  `{"data": {"available": false, "reason": "taken"}}`), `game_flavor` (one of `game.flavors`), `game_version`
  (from `GET /api/public/games/versions`, default `latest`), `eula: true` when `game.requires_eula` (ask the human
  first: they accept the game's licence), and `game_inputs` if the profile asks for any. Example:

```json
{"type": "product", "product_id": 41, "cycle": "monthly", "game_slug": "myserver", "game_flavor": "paper", "game_version": "1.21.4", "eula": true}
```

- Domain: `name`, `action` (`register`, `transfer` or `renew`), `years`, and `epp` (the transfer code) for transfers.
- `gateway`: one `key` from `GET /api/public/gateways?currency=<account currency>`. It only sets how the invoice is paid
  if nobody pays it from credit.

The response (201) contains `order`, `invoice_id`, `invoice_status` and `payment_url`.
Services start once the invoice is paid. Game errors in `errors[]`/422: `eula_required`, `slug_invalid`,
`slug_reserved`, `slug_taken`, `flavor_invalid`, `version_invalid`, `node_out_of_capacity` (the message says which).

## 4. Pay (needs `billing:pay`)

- Pay from credit: `POST /api/client/invoices/{invoice_id}/apply-credit`. This pays the whole invoice or nothing.
  It works only if the account credit covers the balance and the payment fits in the key's monthly limit.
- If it is refused (HTTP 422, `code` starting with `agent_payment_`), give the human the `payment_url` from the
  response. They pay there by card, PayPal or bank transfer. Codes: `agent_payment_credit` (not enough credit),
  `agent_payment_limit` (over the monthly limit), `agent_payment_no_limit` (this key may not pay),
  `agent_payment_currency`, `agent_payment_not_payable`.
- Invoices: `GET /api/client/invoices`, `GET /api/client/invoices/{id}`, PDF at `GET /api/client/invoices/{id}/pdf`.
- You cannot add funds. The human does that at `/client/add-funds`.

## 5. Control services

| What | Request | Permission |
|---|---|---|
| Dashboard summary | `GET /api/client/dashboard` | read |
| List services | `GET /api/client/services` | read |
| Service details (`actions[]` = what it supports) | `GET /api/client/services/{id}` | read |
| Live status | `GET /api/client/services/{id}/status` | read |
| Run an action | `POST /api/client/services/{id}/actions/{action}` | services:manage |
| Follow a queued action | `GET /api/client/services/{id}/actions/{action_id}` | read |
| Monitoring | `GET /api/client/services/{id}/metrics`, `/uptime`, `/events` | read |
| Cancel | `POST /api/client/services/{id}/cancel` with `{"reason": "...", "when": "end_of_period"}` (or `immediate`) | services:cancel |
| Domains | `GET /api/client/domains`, `GET /api/client/domains/{id}` | read |

Actions you may run: `start`, `stop`, `shutdown`, `reboot`, `restart`, `power`, `wol`, `power_status`, `graphs`,
`usage`, `snapshots`, `snapshots_list`, `snapshot_create`, `backup_create`, `rdns_list`, `subaccounts_list`,
`hostname_available`. With `services:destructive` also: `reinstall`, `snapshot_rollback`, `snapshot_delete`,
`backup_restore`, `backup_delete`, `reset`, `change_hostname`, `rdns_set`, `firewall`. Each service lists the ones
it supports in `actions[]`, with their parameters.
Changing actions answer `202` with an action to poll. Read-only ones answer at once.

Queued work (here, on game servers and on web hosting) answers **202** with:

```json
{"action": {"id": 812, "action": "power", "status": "queued", "error": null, "result": {}, "created_at": "2026-10-03T10:00:00+00:00", "finished_at": null}}
```

Poll `GET /api/client/services/{id}/actions/812` every few seconds until `status` is `succeeded`, `failed` or `dry_run`.
Only one action runs per service at a time: a second one while it is busy answers 422.

## 6. Game servers

Find them in `GET /api/client/services` (`product.type: "game"`). Paths below start with
`/api/client/services/{id}/game`. Changes need an active, installed server (else 422).

| What | Request | Permission |
|---|---|---|
| State, address, players, resources | `GET /game` | read |
| Settings (values and schema) | `GET /game/settings` | read |
| Backups | `GET /game/backups` | read |
| List files | `GET /game/files?path=/` | read |
| Installed mods / what the Mods tab offers / search | `GET /game/mods`, `GET /game/mods/info`, `GET /game/mods/search?q=worldedit&page=1` | read |
| Preview a Steam Workshop item | `GET /game/mods/workshop?id=<link or id>` | read |
| Start / stop / restart / kill | `POST /game/power` | games:control |
| Console command | `POST /game/command` | games:control |
| Live console (WebSocket token) | `POST /game/session` with `{"scopes": ["console"]}` | games:control |
| Change settings | `PUT /game/settings` | games:control |
| Take a backup | `POST /game/backups` | games:control |
| Read / write a text file | `GET /game/files/content?path=...`, `PUT /game/files/content` | games:files |
| File operations | `POST /game/files/{op}`, op = `mkdir`, `rename`, `copy`, `compress`, `decompress` | games:files |
| Upload a file | `POST /game/uploads`, or `POST /game/session` with `{"scopes": ["files"]}` for downloads | games:files |
| Install a mod or plugin | `POST /game/mods` | games:mods |
| Switch a mod on or off (Factorio) | `PATCH /game/mods` | games:mods |

Server state:

```http
GET /api/client/services/58/game
```
```json
{"data": {"profile": "minecraft-java", "flavor": "paper", "version": "1.21.4", "slug": "myserver",
  "address": {"host": "myserver.play.nanobyte.host", "port": null, "display": "myserver.play.nanobyte.host"},
  "state": "running", "node": "Atlas", "players": {"online": 3, "max": 20},
  "resources": {"cpu": 25, "ram_used": 1073741824, "ram_total": 4294967296, "disk_used": 2147483648, "disk_total": 21474836480},
  "capabilities": ["power", "console", "files", "settings", "backups", "reinstall", "address", "mods"], "provisioning_step": null}}
```

`state` is `installing`, `failed`, `suspended`, or the game's own state (`running`, `stopped`, `starting`...).

Power (`action`: `start`, `stop`, `restart` or `kill`), answers 202 with an action to poll:

```http
POST /api/client/services/58/game/power
{"action": "restart"}
```

Console command (one line, at most 500 characters):

```http
POST /api/client/services/58/game/command
{"line": "say Restart in 5 minutes"}
```
```json
{"data": {"ok": true}}
```

Live console: `POST /game/session` with `{"scopes": ["console"]}` returns
`{"data": {"agent_url": "https://atlas.example", "token": "sess-...", "expires_at": "..."}}`. Open
`wss://<agent_url host>/v1/ws/console?token=<token>` before `expires_at` (at most 15 minutes), from the same IP.
Most agents only need `POST /game/command` and `GET /game` (players and state).

Settings: `GET /game/settings` returns `{"data": {"values": {"motd": "Hello", "max-players": 20, "pvp": true}, "schema": [{"key": "max-players", "type": "int", "min": 1, "max": 200, "label_key": "games.settings.max-players"}, ...]}}`
(`type`: `string`, `int`, `bool` or `enum` with `options`).
Change only keys listed in `schema`, within its limits (unknown or out-of-range values → 422). Most changes need a restart.

```http
PUT /api/client/services/58/game/settings
{"values": {"motd": "Welcome!", "max-players": 30}}
```

Backups: `GET /game/backups` → `{"data": [{"id": "b-1", "name": "auto", "size": 1000, "created_at": "2026-10-01T00:00:00Z"}]}`.
`POST /game/backups` (no body) takes a new one (202).

Files: paths are relative to the server folder (`""` or `/` is the top). `GET /game/files?path=plugins` →
`{"data": [{"name": "server.properties", "type": "file", "size": 1200, "mtime": 1700000000}]}`.
Text files up to 2 MB:

```http
GET /api/client/services/58/game/files/content?path=server.properties
```
```json
{"data": {"content": "motd=Hello\n"}}
```
```http
PUT /api/client/services/58/game/files/content
{"path": "server.properties", "content": "motd=Hi\n"}
```

File operations: `mkdir {"path"}`, `rename {"path", "to"}`, `copy {"path" or "paths", "to"}`,
`compress {"path" or "paths", "to": "archive.zip"}`, `decompress {"path", "to"?}` → `{"data": {"ok": true}}`.
Upload: `POST /game/uploads` with `{"path": "plugins/My.jar", "size": 1234, "sha256": "<hex>"}` →
`{"data": {"agent_url", "upload_id", "chunk_size", "token"}}`; send the chunks with
`PUT <agent_url>/v1/uploads/<upload_id>/<n>` (`Authorization: Bearer <token>`), then `POST <agent_url>/v1/uploads/<upload_id>/complete`.

Mods: search the server's catalogue, then install by id. The server picks the right file for the game version and
installs dependencies. A URL in the body is refused.

```http
GET /api/client/services/58/game/mods/search?q=worldedit
```
```json
{"data": {"items": [{"project_id": "1u6JkXh5", "title": "WorldEdit", "description": "...", "icon_url": "...", "downloads": 1000, "author": "enginehub", "source": "modrinth"}], "total": 1}}
```
```http
POST /api/client/services/58/game/mods
{"source": "modrinth", "project_id": "1u6JkXh5"}
```
```json
{"data": {"file": "worldedit.jar", "name": "WorldEdit", "source": "modrinth", "installed": [{"file": "worldedit.jar", "name": "WorldEdit"}], "needs_restart": true}}
```

Steam Workshop games: `{"source": "steam_workshop", "workshop_id": "<link or id>", "mod_ids": ["..."]}`.
Restart the server after installing (`needs_restart: true`).

Only with `services:destructive` (else the human, at `/client/services/{id}`): reinstall or change the game/version
(`POST .../game/reinstall`), change the server address (`PUT .../game/address`), restore or delete backups
(`POST .../game/backups/{id}/restore`, `DELETE .../game/backups/{id}`), delete files (`POST .../game/files/delete`),
remove mods (`DELETE .../game/mods`). Cancelling needs `services:cancel`.

## 7. Web hosting

Find hosting accounts in `GET /api/client/services` (`product.type: "shared"` or `"reseller"`). Paths below start with
`/api/client/services/{id}/hosting`.

- `GET /hosting` first. Its `capabilities` say which sections this account's panel supports
  (`{"email": {"read": true, "write": true, "actions": [...]}, ...}`); a section or action it lacks answers 409
  `{"error": "unsupported"}`.
- Every change answers `{"data": {..., "dry_run": false}}`. `dry_run: true` means it was checked but not applied
  (test systems). Long jobs (SSL, backup, zip/unzip, app install) answer 202 with an action to poll.
- File paths are relative to the account's home: `public_html/index.php`. Absolute paths and `..` → 422.
- Names that are not in this account (a mailbox, database, domain, record...) → 404 `{"error": "not_found"}`.
- 422 `{"error": "panel_error"}`: the panel refused (reason in `message`). 503 `{"error": "panel_unavailable"}`: try later.

| What | Request | Permission |
|---|---|---|
| Overview: panel, usage, limits, capabilities | `GET /hosting` | read |
| Domains, redirects | `GET /hosting/domains`, `GET /hosting/redirects` | read |
| Mailboxes, forwarders, autoresponders, catch-all, spam filter | `GET /hosting/email`, `/forwarders`, `/autoresponders`, `/catchall`, `/spam` | read |
| Databases and database users | `GET /hosting/databases`, `GET /hosting/db-users` | read |
| List files | `GET /hosting/files?path=public_html` | read |
| FTP accounts | `GET /hosting/ftp` | read |
| DNS records | `GET /hosting/dns?domain=example.com` | read |
| SSL certificates (no keys) | `GET /hosting/ssl` | read |
| Backups, cron jobs, PHP, logs, stats, apps | `GET /hosting/backups`, `/cron`, `/php`, `/logs?type=error&lines=200`, `/stats`, `/apps` | read |
| Add an addon domain, alias or subdomain | `POST /hosting/domains` | hosting:manage |
| Add a redirect | `POST /hosting/redirects` | hosting:manage |
| Add a forwarder | `POST /hosting/forwarders` | hosting:manage |
| Set an autoresponder | `PUT /hosting/autoresponders/{address}` | hosting:manage |
| Set the catch-all, spam filter | `PUT /hosting/catchall`, `PUT /hosting/spam` | hosting:manage |
| Create a database, set a user's grants | `POST /hosting/databases`, `PUT /hosting/databases/{name}/grants` | hosting:manage |
| Add or change a DNS record | `POST /hosting/dns`, `PUT /hosting/dns/{id}` | hosting:manage |
| Issue a free SSL certificate, install your own | `POST /hosting/ssl/issue`, `PUT /hosting/ssl/custom` | hosting:manage |
| Add or change a cron job | `POST /hosting/cron`, `PUT /hosting/cron/{id}` | hosting:manage |
| PHP version and settings | `PUT /hosting/php` | hosting:manage |
| Take a backup | `POST /hosting/backups` | hosting:manage |
| Install WordPress | `POST /hosting/apps` | hosting:manage |
| Write a text file, upload | `PUT /hosting/files/content`, `POST /hosting/files/upload` (multipart `file`, `path`) | hosting:files |
| File operations | `POST /hosting/files/{op}`, op = `mkdir`, `rename`, `copy`, `move` | hosting:files |
| Zip, unzip | `POST /hosting/files/compress`, `POST /hosting/files/extract` | hosting:files |

Overview:

```http
GET /api/client/services/77/hosting
```
```json
{"data": {"panel": "cpanel", "os": "linux", "live": true,
  "account": {"username": "alice", "domain": "example.com", "ip": "192.0.2.10", "nameservers": ["ns1.example.net"], "home": "/home/alice"},
  "usage": {"disk": {"used": 104857600, "limit": null}, "bandwidth": {"used": 52428800, "limit": 1073741824},
            "mailboxes": {"used": 1, "limit": 10}, "databases": {"used": 1, "limit": null}, "domains": {"used": 2, "limit": 6}},
  "capabilities": {"email": {"read": true, "write": true, "actions": ["create", "update", "delete"]}, "...": {}}}}
```

Disk and bandwidth are bytes. `limit: null` means unlimited.

Request bodies:
- `POST /domains`: `{"type": "addon" | "alias" | "sub", "name": "shop.example.com", "docroot": "shop"}`
- `POST /redirects`: `{"source_domain": "example.com", "path": "/old", "target": "https://example.com/new", "type": 301}`
- `POST /forwarders`: `{"address": "[email protected]", "to": "[email protected]"}`
- `PUT /autoresponders/[email protected]`: `{"subject": "Away", "body": "Back on Monday", "start": "2026-10-03", "stop": "2026-10-06"}`
- `PUT /catchall`: `{"domain": "example.com", "action": "fail" | "blackhole" | "forward", "to": "[email protected]"}`
- `PUT /spam`: `{"enabled": true, "score": 5}`
- `POST /databases`: `{"name": "alice_shop"}`. `PUT /databases/alice_shop/grants`: `{"user": "alice_wp", "privileges": "ALL"}` (or a list like `["SELECT", "INSERT"]`)
- `POST /dns`, `PUT /dns/{id}`: `{"domain": "example.com", "type": "A", "name": "www", "value": "192.0.2.10", "ttl": 300}` (`priority` for MX/SRV).
  Check `zone_managed_here` in `GET /dns?domain=` first: when false, the domain's DNS is elsewhere and edits do nothing.
- `POST /ssl/issue`: `{"domain": "example.com"}` (202). `PUT /ssl/custom`: `{"domain", "cert", "key", "ca"?}` (PEM)
- `POST /cron`, `PUT /cron/{id}`: `{"minute": "*/15", "hour": "*", "day": "*", "month": "*", "weekday": "*", "command": "php ~/public_html/cron.php"}`
- `PUT /php`: `{"domain": "example.com", "version": "ea-php83", "ini": {"memory_limit": "256M"}}` (versions from `GET /php`)
- `POST /apps`: `{"app": "wordpress", "domain": "example.com", "path": "", "title": "My blog", "admin_user": "owner", "admin_email": "[email protected]"}` (202).
  The WordPress admin password goes to the human in the client area, never to you.
- `PUT /files/content`: `{"path": "public_html/index.html", "content": "<h1>Hi</h1>"}` (UTF-8 text, at most 2 MB)
- `POST /files/mkdir`: `{"path": "public_html/new"}`. `rename`: `{"path", "to"}`. `copy`, `move`: `{"path" or "paths", "to": "<folder>"}`
- `POST /files/compress`: `{"paths": ["public_html"], "to": "site.zip"}`. `POST /files/extract`: `{"path": "site.zip", "to": "public_html"}`

Example, add a forwarder:

```http
POST /api/client/services/77/hosting/forwarders
{"address": "[email protected]", "to": "[email protected]"}
```
```json
{"data": {"ok": true, "dry_run": false}}
```

Only the human can, at `/client/services/{id}`: create mailboxes, FTP accounts or database users (they come with
passwords), change any password, open webmail or phpMyAdmin, read or download file contents (they hold database
passwords) and download backups. With `services:destructive` a key may also delete (domains, redirects, mailboxes,
forwarders, autoresponders, databases, database users, files, FTP accounts, DNS records, cron jobs), change file
permissions (`files/chmod`) and restore a backup.

## 8. Support (needs `tickets:write` to write)

- Departments: `GET /api/client/departments`
- Tickets: `GET /api/client/tickets`, `GET /api/client/tickets/{id}`
- Open: `POST /api/client/tickets` with `{"department_id": 1, "subject": "...", "message": "...", "service_id": 42}`
- Reply: `POST /api/client/tickets/{id}/reply` with `{"message": "..."}`. Close: `POST /api/client/tickets/{id}/close`

## Command line: `nanobyte`

Everything above is also available as a command-line tool, for people and for agents that can run commands.

- Install: `curl -fsSL https://nanobyte.host/install.sh | sh` (Linux, macOS) or
  `irm https://nanobyte.host/install.ps1 | iex` (Windows PowerShell).
- Log in: `nanobyte login` shows a code; the human approves it at `/client/cli` and picks the permissions.
  Agents can also use an existing key: `NANOBYTE_API_KEY=<key> nanobyte whoami`.
- Use `--json` for machine-readable output. Anything that orders, pays, cancels or destroys asks for confirmation;
  without a terminal it refuses unless you pass `--yes`, so only pass `--yes` after the human said yes.
- Examples: `nanobyte catalog --type vps`, `nanobyte product nv-2`, `nanobyte quote --product nv-2 --cycle monthly`,
  `nanobyte order --product nv-2 --cycle monthly --option os=ubuntu-24.04 --hostname web1.example.com --pay-credit`,
  `nanobyte services list`, `nanobyte service metrics 42`, `nanobyte restart 42 --wait`, `nanobyte cancel 42 --reason "..."`,
  and `nanobyte api GET /api/client/services/42/hosting/dns` for any endpoint in the appendix.
- Exit codes: 0 ok, 2 usage or missing `--yes`, 3 not allowed (401/403), 4 not found, 5 refused (422), 6 rate limited.
- Keep it current with `nanobyte update` (checksum-verified). `nanobyte <command> --help` documents every command.

## Errors

| Status | Meaning | What to do |
|---|---|---|
| 401 | Key missing, wrong, expired or revoked | Ask the human for a new key |
| 403 `api_key_scope` | The key lacks a permission (`required_ability`), or keys can't do this at all (`required_ability: null`) | Tell the human; they can do it in the client area or make a key with that permission |
| 403 `api_key_client` | The key's account is no longer reachable | Ask for a new key |
| 403 (other) | The service belongs to another account | Check the id |
| 404 | Not found, not on this account, or not this kind of service | Check the id |
| 409 `unsupported` | This hosting panel doesn't offer that section or action | Tell the human |
| 422 | Validation or business rule: `message`, plus `errors{field: [..]}` | Fix the input, or explain the message to the human |
| 429 | Too many requests | Wait and retry later. Don't loop |
| 503 `panel_unavailable` | The hosting panel didn't answer | Try again in a few minutes |

Every response message is safe to show to the human.

## Appendix: every endpoint a key can call

Anything not in this list answers 403 `api_key_scope` with `required_ability: null`, whatever the key's permissions.
`{service}` is a service id. File operations are limited to the ops listed in sections 6 and 7, and service actions to
the list in section 5.

```text
GET    /api/auth/me                                                         read
GET    /api/client/api-key                                                  read
GET    /api/client/dashboard                                                read
GET    /api/client/services                                                 read
GET    /api/client/services/{service}                                       read
GET    /api/client/services/{service}/status                                read
GET    /api/client/services/{service}/actions/{serviceAction}               read
POST   /api/client/services/{service}/actions/{action}                      services:manage
GET    /api/client/services/{service}/hostname/check                        read
GET    /api/client/services/{service}/metrics                               read
GET    /api/client/services/{service}/uptime                                read
GET    /api/client/services/{service}/events                                read
POST   /api/client/services/{service}/cancel                                services:cancel
GET    /api/client/games/slug-check                                         read
GET    /api/client/services/{service}/game                                  read
GET    /api/client/services/{service}/game/settings                         read
GET    /api/client/services/{service}/game/backups                          read
GET    /api/client/services/{service}/game/files                            read
GET    /api/client/services/{service}/game/mods                             read
GET    /api/client/services/{service}/game/mods/info                        read
GET    /api/client/services/{service}/game/mods/search                      read
GET    /api/client/services/{service}/game/mods/workshop                    read
POST   /api/client/services/{service}/game/power                            games:control
POST   /api/client/services/{service}/game/command                          games:control
POST   /api/client/services/{service}/game/session                          games:control
PUT    /api/client/services/{service}/game/settings                         games:control
POST   /api/client/services/{service}/game/backups                          games:control
GET    /api/client/services/{service}/game/files/content                    games:files
PUT    /api/client/services/{service}/game/files/content                    games:files
POST   /api/client/services/{service}/game/files/{op}                       games:files
POST   /api/client/services/{service}/game/uploads                          games:files
POST   /api/client/services/{service}/game/mods                             games:mods
PATCH  /api/client/services/{service}/game/mods                             games:mods
POST   /api/client/services/{service}/game/reinstall                        services:destructive
PUT    /api/client/services/{service}/game/address                          services:destructive
POST   /api/client/services/{service}/game/backups/{backup}/restore         services:destructive
DELETE /api/client/services/{service}/game/backups/{backup}                 services:destructive
DELETE /api/client/services/{service}/game/mods                             services:destructive
GET    /api/client/services/{service}/hosting                               read
GET    /api/client/services/{service}/hosting/domains                       read
GET    /api/client/services/{service}/hosting/redirects                     read
GET    /api/client/services/{service}/hosting/email                         read
GET    /api/client/services/{service}/hosting/forwarders                    read
GET    /api/client/services/{service}/hosting/autoresponders                read
GET    /api/client/services/{service}/hosting/catchall                      read
GET    /api/client/services/{service}/hosting/spam                          read
GET    /api/client/services/{service}/hosting/databases                     read
GET    /api/client/services/{service}/hosting/db-users                      read
GET    /api/client/services/{service}/hosting/files                         read
GET    /api/client/services/{service}/hosting/ftp                           read
GET    /api/client/services/{service}/hosting/dns                           read
GET    /api/client/services/{service}/hosting/ssl                           read
GET    /api/client/services/{service}/hosting/backups                       read
GET    /api/client/services/{service}/hosting/cron                          read
GET    /api/client/services/{service}/hosting/php                           read
GET    /api/client/services/{service}/hosting/logs                          read
GET    /api/client/services/{service}/hosting/stats                         read
GET    /api/client/services/{service}/hosting/apps                          read
POST   /api/client/services/{service}/hosting/domains                       hosting:manage
POST   /api/client/services/{service}/hosting/redirects                     hosting:manage
POST   /api/client/services/{service}/hosting/forwarders                    hosting:manage
PUT    /api/client/services/{service}/hosting/autoresponders/{address}      hosting:manage
PUT    /api/client/services/{service}/hosting/catchall                      hosting:manage
PUT    /api/client/services/{service}/hosting/spam                          hosting:manage
POST   /api/client/services/{service}/hosting/databases                     hosting:manage
PUT    /api/client/services/{service}/hosting/databases/{name}/grants       hosting:manage
POST   /api/client/services/{service}/hosting/dns                           hosting:manage
PUT    /api/client/services/{service}/hosting/dns/{id}                      hosting:manage
POST   /api/client/services/{service}/hosting/ssl/issue                     hosting:manage
PUT    /api/client/services/{service}/hosting/ssl/custom                    hosting:manage
POST   /api/client/services/{service}/hosting/cron                          hosting:manage
PUT    /api/client/services/{service}/hosting/cron/{id}                     hosting:manage
PUT    /api/client/services/{service}/hosting/php                           hosting:manage
POST   /api/client/services/{service}/hosting/backups                       hosting:manage
POST   /api/client/services/{service}/hosting/apps                          hosting:manage
PUT    /api/client/services/{service}/hosting/files/content                 hosting:files
POST   /api/client/services/{service}/hosting/files/upload                  hosting:files
POST   /api/client/services/{service}/hosting/files/{op}                    hosting:files
POST   /api/client/services/{service}/hosting/files/compress                hosting:files
POST   /api/client/services/{service}/hosting/files/extract                 hosting:files
DELETE /api/client/services/{service}/hosting/domains/{name}                services:destructive
DELETE /api/client/services/{service}/hosting/redirects/{id}                services:destructive
DELETE /api/client/services/{service}/hosting/email/{address}               services:destructive
DELETE /api/client/services/{service}/hosting/forwarders                    services:destructive
DELETE /api/client/services/{service}/hosting/autoresponders/{address}      services:destructive
DELETE /api/client/services/{service}/hosting/databases/{name}              services:destructive
DELETE /api/client/services/{service}/hosting/db-users/{name}               services:destructive
DELETE /api/client/services/{service}/hosting/ftp/{user}                    services:destructive
DELETE /api/client/services/{service}/hosting/dns/{id}                      services:destructive
DELETE /api/client/services/{service}/hosting/cron/{id}                     services:destructive
POST   /api/client/services/{service}/hosting/backups/{id}/restore          services:destructive
GET    /api/client/domains                                                  read
GET    /api/client/domains/{domain}                                         read
POST   /api/client/orders                                                   orders:place
GET    /api/client/invoices                                                 read
GET    /api/client/invoices/{invoice}                                       read
GET    /api/client/invoices/{invoice}/pdf                                   read
GET    /api/client/transactions                                             read
POST   /api/client/invoices/{invoice}/apply-credit                          billing:pay
POST   /api/client/invoices/{invoice}/pay                                   billing:pay
GET    /api/client/departments                                              read
GET    /api/client/tickets                                                  read
GET    /api/client/tickets/{ticket}                                         read
GET    /api/client/tickets/{ticket}/attachments/{reply}/{index}             read
POST   /api/client/tickets                                                  tickets:write
POST   /api/client/tickets/{ticket}/reply                                   tickets:write
POST   /api/client/tickets/{ticket}/close                                   tickets:write
```