API documentation
Your agent can host itself
The whole platform is an email address and a REST API away: register, top up with crypto, deploy a fleet, destroy it when done. No human in the loop required - this page tells your agent everything it needs.
1. Point it at one URL
Give your agent /llms.txt - a plain-text version of this page that LLM tools fetch and follow out of the box.
2. Or paste the context
Copy the full instruction block below straight into your agent's system prompt or tool description. It is self-contained.
3. It does the rest
Register → check balance → deploy → poll until ACTIVE → SSH as root → destroy. Hourly billing means experiments cost cents.
# NoctHost - API guide for AI agents
> NoctHost is a no-KYC, crypto-funded layer over top-tier clouds: it provisions
> REAL cloud servers (NVMe, dedicated IPv4, root SSH) on Vultr, DigitalOcean,
> Hetzner and Linode: via their APIs, billed by the hour. It is NOT a
> reseller or a Proxmox host. An email is the whole identity. REST API over JSON.
>
> What can be deployed: shared-vCPU VPS, dedicated-CPU machines (cores not
> shared with anyone), NVIDIA GPU servers by the hour, and whole physical
> machines (bare metal, no hypervisor, no snapshots).
>
> Paying: crypto first. USDT (Tron, BSC, Ethereum, Polygon) from $5 and 300+
> other coins from $20, no KYC. Cards (Visa, Mastercard, plus Apple Pay or
> Google Pay where the browser supports it) from $20, processed by our payment
> partner, which collects billing details. Top-ups are made in the dashboard.
>
> Base URL: https://api.nocthost.com
> Human docs: /docs · This file: /llms.txt
## Happy path (deploy one server)
1. POST /auth/register {"email":"...","password":"min 8 chars"}
-> 201 {"accessToken":"<jwt>","user":{...}} (or POST /auth/login if registered)
2. Send "Authorization: Bearer <accessToken>" on every request below.
For scripts, create a non-expiring API key in the dashboard (Profile ->
API keys) and send it the same way: "Authorization: Bearer nh_...".
3. GET /billing -> {"balance":"10.00","hourlySpend":"0"}
Balance must cover >= 1 hour of the chosen plan, else POST /servers returns 402.
4. POST /servers {"label":"my-bot","planId":"vc2-1c-1gb","regionId":"ams","password":"<root pw>"}
-> 202 {"server":{"id":"...","status":"PROVISIONING","ipv4":null,...}}
The password sets the server's root login; it is never stored, so keep it.
5. Poll GET /servers/<id> every ~5s until "status":"ACTIVE" (typically ~60s).
Then "ipv4" is set - SSH in as root with the password you chose.
6. DELETE /servers/<id> -> 202. Billing for it stops the same hour.
## Endpoints
POST /auth/register
Create an account. Email + password is all that is needed.
body: {"email":"agent@example.com","password":"min-8-chars"}
returns: 201 {"accessToken":"<jwt>","user":{"id","email","balance"}}
POST /auth/login
Get a fresh access token (expires in 15 minutes).
body: {"email":"...","password":"..."}
returns: 200 {"accessToken":"<jwt>","user":{...}}
GET /auth/me (auth)
Whoami: id, email and current balance.
returns: 200 {"user":{"id","email","balance":"9.96"}}
GET /billing (auth)
Balance and total hourly spend across active servers.
returns: 200 {"balance":"9.96","hourlySpend":"0.012"}
GET /servers (auth)
List all non-destroyed servers.
returns: 200 {"servers":[{"id","label","status","region","plan","ipv4","hourlyPrice","createdAt"}]}
GET /servers/:id (auth)
One server. Poll this every ~5s after a deploy.
returns: 200 {"server":{...,"status":"ACTIVE","ipv4":"203.0.113.10"}}
GET /servers/providers (auth)
Which backends can take a deploy right now, and which of their regions are temporarily out of capacity. Check this before picking providerId/regionId.
returns: 200 {"providers":[{"id":"vultr","label","available":true}],"outages":{"hetzner":{"fra":"..."}}}
POST /servers (auth)
Deploy one server. Async: 202 = accepted, not yet running. Optional osId (see the OS list in the catalog below, default Ubuntu 22.04) or appSlug (one-click app, Vultr only) - pick at most one. osId works on every provider; each supports a subset of the list, and an OS the chosen provider does not carry returns 400. Optional sshKey installs a public key for root at first boot.
body: {"label":"my-bot","planId":"vc2-1c-1gb","regionId":"ams","osId":1743,"providerId":"vultr","password":"12-72 chars, no spaces","sshKey":"ssh-ed25519 AAAA... you@host"}
returns: 202 {"server":{...,"status":"PROVISIONING"}}
POST /servers/bulk (auth)
Deploy up to 10 servers in one call. Labels get -1..-N suffixes.
body: {"label":"fleet","count":10,"planId":"vc2-1c-1gb","regionId":"ams","password":"applied to all","sshKey":"optional, applied to all"}
returns: 202 {"servers":[...10 items, all "PROVISIONING"]}
POST /servers/:id/reboot (auth)
Reboot a running server.
returns: 200 {"server":{...}}
POST /servers/:id/stop (auth)
Power off a server. STOPPED is still billed while it exists.
returns: 200 {"server":{...,"status":"STOPPED"}}
POST /servers/:id/start (auth)
Power a STOPPED server back on.
returns: 200 {"server":{...,"status":"ACTIVE"}}
POST /servers/:id/root-password (auth)
Set a new root password without erasing the disk, where the server page shows "Change root password". The server must be running; Linode servers without our recovery key must be stopped first. On Hetzner without the recovery key the password you send is ignored: Hetzner generates one and returns it (matchesRequested: false). The password is shown only in this response.
body: {"password":"12-72 chars, no spaces"}
returns: 200 {"password":"...","matchesRequested":true,"note":"..."}
DELETE /servers/:id (auth)
Destroy a server. Billing stops the same hour.
returns: 202 {"ok":true}
GET /backups (auth)
List your backups (full disk snapshots, kept 7 days).
returns: 200 {"backups":[{"id","label","status","region","plan","createdAt","expiresAt"}]}
POST /backups (auth)
Back up a server. Async: snapshot takes minutes, PENDING -> AVAILABLE.
body: {"serverId":"<id>"}
returns: 202 {"backup":{...,"status":"PENDING"}}
POST /backups/:id/restore (auth)
Restore an AVAILABLE backup onto a new server (gets a new IP). password is required: the root password of the restored server (12–72 Latin chars, 3 of lower/upper/digits/symbols, no spaces). Optional planId restores onto a bigger plan.
body: {"password":"<root password>","planId":"<optional>"}
returns: 202 {"server":{...,"status":"PROVISIONING"}}
DELETE /backups/:id (auth)
Delete a backup before its 7-day expiry.
returns: 202 {"ok":true}
## Catalog
Two kinds of planId work, and this is the part agents get wrong most often.
1. Our own eight tiers, priced the same on every provider (list below).
2. Any provider-native size id from the wider catalog, passed as planId exactly
as the provider names it: c-2, g2-gpu-rtx4000a1-s. That is where dedicated-CPU
machines and GPU cards live. In total 225 native sizes, $10 to $7000/mo: 127 with dedicated cores, 17 with an NVIDIA card (GPU is priced by the hour, from $0.085/hr).
GET /servers/providers returns what is deployable right now; a size that the
provider does not currently allocate to us is refused at deploy time.
plans: vc2-1c-1gb Micro (1 vCPU / 1 GB RAM / 25 GB SSD / 500 GB traffic, $10/mo) · vc2-1c-2gb Starter (1 vCPU / 2 GB RAM / 50 GB SSD / 2048 GB traffic, $18/mo) · vc2-2c-4gb Standard (2 vCPU / 4 GB RAM / 80 GB SSD / 3072 GB traffic, $34/mo) · vc2-4c-8gb Pro (4 vCPU / 8 GB RAM / 160 GB SSD / 4096 GB traffic, $62/mo) · vc2-6c-16gb Beast (6 vCPU / 16 GB RAM / 320 GB SSD / 5120 GB traffic, $120/mo) · vc2-8c-32gb Titan (8 vCPU / 32 GB RAM / 640 GB SSD / 6144 GB traffic, $240/mo) · vc2-16c-64gb Colossus (16 vCPU / 64 GB RAM / 1280 GB SSD / 10240 GB traffic, $480/mo) · vc2-24c-96gb Leviathan (24 vCPU / 96 GB RAM / 1600 GB SSD / 15360 GB traffic, $960/mo)
regions: ams fra cdg lhr man mad waw sto hel nbg · ewr ord dfw atl mia sea lax sjc yto mex · sao scl · nrt itm icn sgp blr bom del · syd mel · jnb tlv
os: 2760 Ubuntu 26.04 LTS (vultr) · 2284 Ubuntu 24.04 LTS (all providers) · 1743 Ubuntu 22.04 LTS (all providers) · 2625 Debian 13 (Trixie) (vultr) · 2136 Debian 12 (Bookworm) (vultr, hetzner, linode) · 477 Debian 11 (Bullseye) (vultr, hetzner, linode) · 2593 AlmaLinux 10 (vultr) · 1868 AlmaLinux 9 (all providers) · 2594 Rocky Linux 10 (vultr) · 1869 Rocky Linux 9 (all providers) · 2467 CentOS Stream 10 (vultr) · 542 CentOS Stream 9 (all providers) · 2752 Fedora 44 (vultr, digitalocean) · 2658 Fedora 43 (vultr, linode) · 501 Windows Server 2022 (vultr, Windows: extra licence fee, RDP instead of SSH, needs a plan with >= 4 GB RAM) · 371 Windows Server 2019 (vultr, Windows: extra licence fee, RDP instead of SSH, needs a plan with >= 4 GB RAM) · 2514 Windows Server 2025 (vultr, Windows: extra licence fee, RDP instead of SSH, needs a plan with >= 4 GB RAM)
osId is optional; omit it and you get 1743 Ubuntu 22.04 LTS. Each provider carries
a subset of the list (shown in brackets) - asking for an OS the chosen provider
does not have returns 400, so either pick one marked "all providers" or match it
to your providerId. Regions and plans work the same way: not every provider
serves every city. GET /servers/providers says which backends can take a deploy
right now and which of their regions are temporarily out of capacity.
Hourly price is returned on the server object at deploy time and never changes
for a running server.
## Server statuses
PROVISIONING -> ACTIVE (success) | ERROR (failed, not billed further)
ACTIVE <-> STOPPED (powered off via /servers/:id/stop or /start; STOPPED is still billed while it exists)
DESTROYING -> DESTROYED
SUSPENDED = balance ran out or abuse hold; server is off but recoverable.
ARCHIVED = balance ran out; the instance is deleted but your data is kept in a snapshot. Top up to restore.
## Backups
On-demand backups are Vultr-only for now (DigitalOcean/Linode cannot be backed up on demand). Full disk snapshots, kept 7 days, then auto-deleted.
POST /backups {"serverId"} -> snapshot (PENDING -> AVAILABLE, takes minutes).
POST /backups/<id>/restore {"password", "planId"?} -> new server from the snapshot (new IP, same region; same plan unless planId). password (root password of the new server) is required.
Backup statuses: PENDING -> AVAILABLE | ERROR; EXPIRED once deleted.
## Errors
400 validation failed ("details" in body) | 401 token expired -> re-login, retry once
402 insufficient balance -> top up | 403 suspended or server limit (default 10, lower during promos)
404 not yours / not found | 409 already being destroyed
429 rate limited (100 req/min, 10/min on /auth/*) -> exponential backoff
## Rules for agents
- Deploys are asynchronous. 202 means accepted; poll GET /servers/:id every ~5 seconds until status is ACTIVE (typically ~60s) or ERROR.
- For automation, prefer a personal API key over login: create one in the dashboard under Profile, then send it as "Authorization: Bearer nh_...". It does not expire, is revocable, and needs no re-login. Login-issued access tokens (JWT) still work but expire after 15 minutes.
- Access tokens (from POST /auth/login) expire after 15 minutes. On a 401 with a JWT, call POST /auth/login again and retry once. API keys (nh_...) do not expire - a 401 there means the key was revoked.
- Money values (balance, hourlyPrice, hourlySpend) are strings - parse as decimal, never float-math them.
- Check GET /billing before deploying: the balance must cover at least one hour per server, or you get 402.
- Server labels must match ^[a-zA-Z0-9._-]+$ (max 64 chars).
- sshKey takes one public key line (ssh-ed25519, ssh-rsa, ecdsa-sha2-nistp256/384/521 or the sk- hardware variants) and installs it for root at first boot, on POST /servers and POST /servers/bulk alike. It does not replace password: that stays required, because the provider's web console and account recovery both run on it, and a key with a typo in it would otherwise lock you out of a server you just paid for. Turn password login off inside the server once your key works. Windows servers ignore it, they use RDP.
- providerId is optional and defaults to "vultr". "digitalocean", "hetzner" and "linode" are also available, each Linux-only and each serving a subset of the regions, plans and OS ids. Sending an unsupported combination returns 400; GET /servers/providers lists what each one takes right now.
- The default account limit is 10 active servers (temporarily lower, e.g. 5, during a promo) and can be raised. 403 with a limit message means destroy something first or ask for a raise via /docs/custom-requests.
- Rate limit is 100 requests/minute (10/min on auth routes). On 429, back off exponentially.
- Always destroy servers you no longer need - billing is hourly and stops only at DELETE.
- On-demand backups (POST /backups) are Vultr-only for now, DigitalOcean/Linode servers cannot be backed up on demand (automatic snapshots on suspension still work on any provider). Backups are full disk snapshots kept 7 days. POST /backups is async (PENDING -> AVAILABLE, minutes); only restore once AVAILABLE. Restore creates a new server with a new IP.
Authentication
Every request marked auth carries a bearer token. There are two kinds, and they go in the same header.
API key: for scripts and agents
Create one in Profile → API keys. It starts with nh_, never expires, and keeps working while you sleep. Revoke it any time — anything using it stops immediately.
curl https://api.nocthost.com/servers \
-H "Authorization: Bearer $NOCTHOST_KEY"Access token: for interactive sessions
POST /auth/login returns a short-lived token (15 minutes). Fine for a one-off script you run by hand; for anything unattended use an API key instead.
curl -X POST https://api.nocthost.com/auth/login \
-H "content-type: application/json" \
-d '{"email":"you@example.com","password":"…"}'Admin endpoints are the exception: they require an access token and reject API keys.
Endpoint reference
JSON in, JSON out. Authorization: Bearer <accessToken> on everything marked auth.
/auth/registerCreate an account. Email + password is all that is needed.
body: {"email":"agent@example.com","password":"min-8-chars"}
returns: 201 {"accessToken":"<jwt>","user":{"id","email","balance"}}
/auth/loginGet a fresh access token (expires in 15 minutes).
body: {"email":"...","password":"..."}
returns: 200 {"accessToken":"<jwt>","user":{...}}
/auth/meauthWhoami: id, email and current balance.
returns: 200 {"user":{"id","email","balance":"9.96"}}
/billingauthBalance and total hourly spend across active servers.
returns: 200 {"balance":"9.96","hourlySpend":"0.012"}
/serversauthList all non-destroyed servers.
returns: 200 {"servers":[{"id","label","status","region","plan","ipv4","hourlyPrice","createdAt"}]}
/servers/:idauthOne server. Poll this every ~5s after a deploy.
returns: 200 {"server":{...,"status":"ACTIVE","ipv4":"203.0.113.10"}}
/servers/providersauthWhich backends can take a deploy right now, and which of their regions are temporarily out of capacity. Check this before picking providerId/regionId.
returns: 200 {"providers":[{"id":"vultr","label","available":true}],"outages":{"hetzner":{"fra":"..."}}}
/serversauthDeploy one server. Async: 202 = accepted, not yet running. Optional osId (see the OS list in the catalog below, default Ubuntu 22.04) or appSlug (one-click app, Vultr only) - pick at most one. osId works on every provider; each supports a subset of the list, and an OS the chosen provider does not carry returns 400. Optional sshKey installs a public key for root at first boot.
body: {"label":"my-bot","planId":"vc2-1c-1gb","regionId":"ams","osId":1743,"providerId":"vultr","password":"12-72 chars, no spaces","sshKey":"ssh-ed25519 AAAA... you@host"}
returns: 202 {"server":{...,"status":"PROVISIONING"}}
/servers/bulkauthDeploy up to 10 servers in one call. Labels get -1..-N suffixes.
body: {"label":"fleet","count":10,"planId":"vc2-1c-1gb","regionId":"ams","password":"applied to all","sshKey":"optional, applied to all"}
returns: 202 {"servers":[...10 items, all "PROVISIONING"]}
/servers/:id/rebootauthReboot a running server.
returns: 200 {"server":{...}}
/servers/:id/stopauthPower off a server. STOPPED is still billed while it exists.
returns: 200 {"server":{...,"status":"STOPPED"}}
/servers/:id/startauthPower a STOPPED server back on.
returns: 200 {"server":{...,"status":"ACTIVE"}}
/servers/:id/root-passwordauthSet a new root password without erasing the disk, where the server page shows "Change root password". The server must be running; Linode servers without our recovery key must be stopped first. On Hetzner without the recovery key the password you send is ignored: Hetzner generates one and returns it (matchesRequested: false). The password is shown only in this response.
body: {"password":"12-72 chars, no spaces"}
returns: 200 {"password":"...","matchesRequested":true,"note":"..."}
/servers/:idauthDestroy a server. Billing stops the same hour.
returns: 202 {"ok":true}
/backupsauthList your backups (full disk snapshots, kept 7 days).
returns: 200 {"backups":[{"id","label","status","region","plan","createdAt","expiresAt"}]}
/backupsauthBack up a server. Async: snapshot takes minutes, PENDING -> AVAILABLE.
body: {"serverId":"<id>"}
returns: 202 {"backup":{...,"status":"PENDING"}}
/backups/:id/restoreauthRestore an AVAILABLE backup onto a new server (gets a new IP). password is required: the root password of the restored server (12–72 Latin chars, 3 of lower/upper/digits/symbols, no spaces). Optional planId restores onto a bigger plan.
body: {"password":"<root password>","planId":"<optional>"}
returns: 202 {"server":{...,"status":"PROVISIONING"}}
/backups/:idauthDelete a backup before its 7-day expiry.
returns: 202 {"ok":true}
Catalog IDs
Plans: vc2-1c-1gb Micro (1 vCPU / 1 GB RAM / 25 GB SSD / 500 GB traffic, $10/mo) · vc2-1c-2gb Starter (1 vCPU / 2 GB RAM / 50 GB SSD / 2048 GB traffic, $18/mo) · vc2-2c-4gb Standard (2 vCPU / 4 GB RAM / 80 GB SSD / 3072 GB traffic, $34/mo) · vc2-4c-8gb Pro (4 vCPU / 8 GB RAM / 160 GB SSD / 4096 GB traffic, $62/mo) · vc2-6c-16gb Beast (6 vCPU / 16 GB RAM / 320 GB SSD / 5120 GB traffic, $120/mo) · vc2-8c-32gb Titan (8 vCPU / 32 GB RAM / 640 GB SSD / 6144 GB traffic, $240/mo) · vc2-16c-64gb Colossus (16 vCPU / 64 GB RAM / 1280 GB SSD / 10240 GB traffic, $480/mo) · vc2-24c-96gb Leviathan (24 vCPU / 96 GB RAM / 1600 GB SSD / 15360 GB traffic, $960/mo)
Regions: ams fra cdg lhr man mad waw sto hel nbg · ewr ord dfw atl mia sea lax sjc yto mex · sao scl · nrt itm icn sgp blr bom del · syd mel · jnb tlv
OS (osId): 2760 Ubuntu 26.04 LTS (vultr) · 2284 Ubuntu 24.04 LTS (all providers) · 1743 Ubuntu 22.04 LTS (all providers) · 2625 Debian 13 (Trixie) (vultr) · 2136 Debian 12 (Bookworm) (vultr, hetzner, linode) · 477 Debian 11 (Bullseye) (vultr, hetzner, linode) · 2593 AlmaLinux 10 (vultr) · 1868 AlmaLinux 9 (all providers) · 2594 Rocky Linux 10 (vultr) · 1869 Rocky Linux 9 (all providers) · 2467 CentOS Stream 10 (vultr) · 542 CentOS Stream 9 (all providers) · 2752 Fedora 44 (vultr, digitalocean) · 2658 Fedora 43 (vultr, linode) · 501 Windows Server 2022 (vultr, Windows: extra licence fee, RDP instead of SSH, needs a plan with >= 4 GB RAM) · 371 Windows Server 2019 (vultr, Windows: extra licence fee, RDP instead of SSH, needs a plan with >= 4 GB RAM) · 2514 Windows Server 2025 (vultr, Windows: extra licence fee, RDP instead of SSH, needs a plan with >= 4 GB RAM)
Omit osId and you get 1743 Ubuntu 22.04 LTS. The brackets say which backends carry the image; asking a backend for one it does not have returns 400.
Rules your agent should follow
- ▸Deploys are asynchronous. 202 means accepted; poll GET /servers/:id every ~5 seconds until status is ACTIVE (typically ~60s) or ERROR.
- ▸For automation, prefer a personal API key over login: create one in the dashboard under Profile, then send it as "Authorization: Bearer nh_...". It does not expire, is revocable, and needs no re-login. Login-issued access tokens (JWT) still work but expire after 15 minutes.
- ▸Access tokens (from POST /auth/login) expire after 15 minutes. On a 401 with a JWT, call POST /auth/login again and retry once. API keys (nh_...) do not expire - a 401 there means the key was revoked.
- ▸Money values (balance, hourlyPrice, hourlySpend) are strings - parse as decimal, never float-math them.
- ▸Check GET /billing before deploying: the balance must cover at least one hour per server, or you get 402.
- ▸Server labels must match ^[a-zA-Z0-9._-]+$ (max 64 chars).
- ▸sshKey takes one public key line (ssh-ed25519, ssh-rsa, ecdsa-sha2-nistp256/384/521 or the sk- hardware variants) and installs it for root at first boot, on POST /servers and POST /servers/bulk alike. It does not replace password: that stays required, because the provider's web console and account recovery both run on it, and a key with a typo in it would otherwise lock you out of a server you just paid for. Turn password login off inside the server once your key works. Windows servers ignore it, they use RDP.
- ▸providerId is optional and defaults to "vultr". "digitalocean", "hetzner" and "linode" are also available, each Linux-only and each serving a subset of the regions, plans and OS ids. Sending an unsupported combination returns 400; GET /servers/providers lists what each one takes right now.
- ▸The default account limit is 10 active servers (temporarily lower, e.g. 5, during a promo) and can be raised. 403 with a limit message means destroy something first or ask for a raise via /docs/custom-requests.
- ▸Rate limit is 100 requests/minute (10/min on auth routes). On 429, back off exponentially.
- ▸Always destroy servers you no longer need - billing is hourly and stops only at DELETE.
- ▸On-demand backups (POST /backups) are Vultr-only for now, DigitalOcean/Linode servers cannot be backed up on demand (automatic snapshots on suspension still work on any provider). Backups are full disk snapshots kept 7 days. POST /backups is async (PENDING -> AVAILABLE, minutes); only restore once AVAILABLE. Restore creates a new server with a new IP.
Servers for humans work here too
The same API powers the dashboard. Try it by hand first - deploy, poke around, destroy. Cents per hour.