Joy REST API v1
Everything in the console is available over HTTPS with a Bearer token: quotes, deployments, power actions, reinstalls, snapshots, live stats, invoices and tickets. Responses are JSON, endpoints are versioned and additive — v1 will not break under you.
Overview
The API mirrors the console: what you can click, you can call. All endpoints live under http://127.0.0.1/api/v1, accept and return application/json, and are authenticated with a Bearer token that you create in the console. Deployments are asynchronous — you receive a job id and poll it — everything else answers immediately.
| Method | Endpoint | Scope | Purpose |
|---|---|---|---|
| GET | /me | read | Account profile, wallet balance and the scopes of the calling token |
| GET | /locations | read | Regions with live availability, IPv4 stock, price multiplier and installable OS codes |
| GET | /plans | read | Fixed plans (USD), component prices for custom builds and billing cycles |
| GET | /os | read | OS catalogue (code, name, family) |
| POST | /quote | read | Price a plan or custom spec without ordering |
| GET | /instances | read | List your servers with live hypervisor status |
| POST | /instances | orders.write | Order + deploy a server; pays from the wallet by default |
| GET | /instances/{id} | read | Full detail incl. IPs, credentials, billing and last job |
| GET | /instances/{id}/stats | read | Live snapshot plus a recent time-series (CPU, network, RAM) |
| POST | /instances/{id}/{action} | instances.write | start · stop · shutdown · reboot · reset · reinstall · password · snapshot · rename |
| DELETE | /instances/{id} | instances.write | Terminate the server and release its IPs (requires confirm) |
| GET | /jobs/{id} | read | Poll an asynchronous job |
| GET | /billing | billing.read | Wallet, currency, unpaid invoices, monthly run-rate |
| GET | /invoices | billing.read | Last 100 invoices with pay links |
| GET | /tickets | read | Your support tickets |
| POST | /tickets | tickets.write | Open a ticket |
| GET | /status | read | Platform status plus the health of your own instances |
Unknown paths under /api/ return 404 {"ok":false,"error":"Unknown endpoint — see /api-integration"}.
Authentication & scopes
Send the token in the Authorization header. Tokens are 20–200 characters, start with joy_ and are stored hashed — if you lose one, revoke it and create another. Each token carries a comma-separated list of scopes; a token with the * scope can do everything. Any token with at least one scope satisfies read.
| Scope | Grants |
|---|---|
read | All GET endpoints, POST /quote |
orders.write | POST /instances — creating orders and paying them from the wallet |
instances.write | Power actions, reinstall, password reset, snapshots, rename, DELETE /instances/{id} |
billing.read | GET /billing, GET /invoices |
tickets.write | POST /tickets |
A token can also be limited to an IP allow-list (CIDR list) and given an expiry date. Requests from other addresses get 403 IP not allowed for this token; expired tokens get 401 Token expired. API access can be disabled for a whole account from the console (403 API disabled for this account).
Errors, limits & conventions
- Every response has
ok: true|false. Failures also carryerror(human-readable) andstatus(the HTTP code). - Rate limit: 600 requests per minute per token →
429withRetry-After. Deployments are limited to 10 per hour per account, ticket creation to 5 per hour, power actions to 10 per minute per instance and password resets to 3 per 10 minutes. - Validation errors return
422. Missing resources return404. An order that could not be paid from the wallet returns402with apay_url. - Money is always USD as a float (
total_usd); where a local amount is included it is labelled (total_local,currency). Timestamps are UTCYYYY-MM-DD HH:MM:SS. - Instance ids are opaque 32-character hashes (
ihash), never the hypervisor VM id. - Every call is logged (method, path, status, IP, duration) and visible under Console → Account → API tokens.
Account
GET/me — who am I, and what can this token do?
Catalogue & quotes
GET/locations returns regions with available (true when a node is online, a template is verified and IPv4 is in stock), ipv4_stock, price_multiplier and the OS codes installable there.
GET/plans returns three arrays: plans (code, name, family, cores, ram_mb, disk_gb, bandwidth_tb, price_usd_month, price_usd_hour, windows_addon_usd), components keyed by component (unit_price_usd, unit, min, max, step — for custom builds) and cycles (code, months, discount_pct). GET/os lists operating systems.
POST/quote prices a spec without ordering. Send either plan or custom:
| Field | Type | Notes |
|---|---|---|
location | string | Location code from /locations (required) |
os | string | OS code, e.g. ubuntu-24, win-2022 (required) |
plan | string | Plan code — omit when sending custom |
custom | object | {"cores":4,"ram_gb":8,"disk_gb":120} within the component ranges |
cycle | string | hourly · monthly (default) · quarterly · semiannual · annual |
extra_ipv4 | int | 0–8 additional addresses |
backup_gb | int | Off-node backup space in 10 GB steps |
port_10g | bool | Upgrade to a 10 Gbps port |
Instances
GET/instances lists your servers. Each instance object has the shape below; live is null when the hypervisor has not reported yet, and stale is true when the last report is older than a minute.
POST/instances takes the same body as /quote plus hostname (required, DNS label), optional ssh_key (public key, Linux only) and pay (wallet — default — pays the invoice immediately from your balance; anything else leaves it unpaid with a pay_url). Deployment starts the moment the invoice is paid. Rate limit: 10 deployments per hour.
GET/instances/{id} adds ips[] (ip, gateway, netmask, rdns), credentials (user = root or Administrator, password) and last_job. Treat this response as secret. GET/instances/{id}/stats returns the live snapshot plus series[] of {ts, cpu_pct, net_in_bps, net_out_bps, mem_bytes} points collected every few seconds for the last ~15 minutes.
Power, reinstall, snapshots
POST/instances/{id}/{action} — all actions except rename are asynchronous and answer 202 with a job id and a poll URL.
| Action | Body | Effect |
|---|---|---|
start · stop · shutdown · reboot · reset | — | Power on, hard stop, ACPI shutdown, ACPI reboot, hard reset. 10 per minute per instance. |
reinstall | {"os":"ubuntu-22","hostname":"optional"} | Wipes the disk, keeps the IP and VMID, installs the template and generates a new password. |
password | — | Generates and applies a new root / Administrator password through the guest agent. 3 per 10 minutes. |
snapshot | {"op":"create|rollback|delete","name":"pre-upgrade"} | NVMe snapshots on the node. Free; not a backup. |
rename | {"hostname":"web-02"} | Synchronous. Lower-case DNS label, 1–63 chars. |
DELETE/instances/{id} terminates the server and releases its IPs. To prevent accidents the body must repeat the hostname: {"confirm":"api-01"}; otherwise 422 Send {"confirm": "<hostname>"} to destroy. Hourly billing stops immediately; prepaid terms are not refunded.
Jobs
Every asynchronous action returns a job id. GET/jobs/{id} returns status (queued → running → done | failed), step, progress (0–100), type, instance, created_at and finished_at. Deployment steps are clone → configure → boot → agent → network → ready. Poll every 2–5 seconds; a typical deployment finishes in 60–120 seconds.
Billing & invoices
GET/billing (scope billing.read) returns wallet_usd, currency, unpaid[] (id, no, total, currency, total_usd, due, pay_url) and monthly_run_rate_usd — the sum of all active services' monthly prices. GET/invoices returns the last 100 invoices with kind, status, totals, dates and a console URL. Invoices cannot be paid through the API; use the pay_url or top up the wallet.
Tickets
GET/tickets lists your tickets (id, ref, subject, status, priority, department, url). POST/tickets (scope tickets.write, 5 per hour) opens one:
Status
GET http://127.0.0.1/status.json is public, needs no token and is cached for 30 seconds — use it for dashboards and monitors. GET/status on the API returns the same components and incidents plus my_instances[] with each server's status and live hypervisor state.
Examples — curl · PHP · JavaScript
A complete flow: check the region, quote, deploy on hourly billing, poll the job, print the IP and password, and destroy the server at the end.
Legacy Instances API (/api/connect)
x-api-token / x-api-key pair; it is not extended with new features.The legacy API is a single JSON POST endpoint at https://api.joy.services/api/connect/. Every request selects an operation with the type field and is authenticated with two headers. Treat both values as secrets: do not expose them in public repositories, client-side apps or browser code.
| Header | Requirement | Purpose |
|---|---|---|
Accept | Recommended | Ensures a JSON response (application/json) |
Content-Type | Required | Requests must be JSON |
User-Agent | Recommended | Used for logging / telemetry (joyconnect) |
x-api-token | Required | Primary token identifier, e.g. JOY-API-TOKEN#API |
x-api-key | Required | Key associated with the token, e.g. joy-xxx… |
Body fields: type (required — dashboardinfo, account_info, machine_list, machine_info, change_machine_state), init_ip and init_browser (recommended — the client IP and browser/app identifier captured by your app).
type = dashboardinfo
Fetches dashboard totals: amount (wallet, string), instances, iram (total RAM), icore (total vCPU), hostings, databases, dns_domains, cdn_domains, total. Note that success is returned as the string "true"/"false" in this format; treat numeric fields as integers.
type = account_info
Returns the account profile: email, verify (1 = verified), twofa (1 = enabled), username, full_name, gender, profile_pic, ip, time, and an api_token object (api_token, api_key, api_reg_ip, status enabled/disabled).
type = machine_list
Lists active compute instances in value[]: id, machine (short id), machine_node (node label), ip, platform (ubuntu / windows), ram, core, storage, expire, extime, price and ihash. Use the ihash as instance_token for the calls below.
type = machine_info
Real-time VM details for one instance (instance_token required). Current holds runtime metrics — status, vmid, cpu (fractional; multiply by 100 for %), mem / maxmem / freemem (bytes), uptime (s), netin / netout, diskread / diskwrite, maxdisk (bytes), nics per interface — and qemuConfig holds the VM configuration (cores, memory, net0, scsi0, …). osinfo may be null. Some values arrive as strings depending on serialization; coerce types in your client.
type = change_machine_state
Changes the power state with machine_state; unsupported values return {"success": false, "error": "invalid_state"}.
| State | Action | Success value |
|---|---|---|
start | Power on | machine_already_started |
shutdown | Graceful shutdown | shutdown_success |
force_shutdown | Hard stop | stopped_successfully |
reboot | Graceful reboot | reboot_success |
force_reboot | Hard reset | power_reset_success |
pause | Suspend | machine_paused |
hibernate | Hibernate | machine_hibernated |
resume | Resume | machine_already_resumed |
Errors. Typical failure causes are missing or invalid headers, an invalid JSON body, an unsupported type, or a request blocked by server-side rules. Check the HTTP status code and parse JSON safely; your backend may return JSON as an encoded string in some layers, so if the first parse returns a string, parse again.
Security notes. If you are building a public website or a client-side SPA, route requests through your backend and keep tokens server-side. Use HTTPS, rotate keys if compromised, restrict allowed IPs where supported, and log request IDs for support.
FAQ & changelog
Is there a sandbox?
Are there webhooks or a Terraform provider?
/jobs/{id} for now; webhooks for job completion and invoice events are on the roadmap and will be announced in the Updates feed. Tell us in a ticket what you need — the API is versioned and additive.How do I migrate from the legacy API?
machine_list → GET /instances (the ihash is the same id); machine_info → GET /instances/{id} and /stats; change_machine_state → POST /instances/{id}/start|stop|shutdown|reboot|reset; dashboardinfo / account_info → GET /me and GET /billing.Can I restrict a token to one server?
instances.write only and an IP allow-list for the host that runs the script.Changelog
/api/connect remains available for existing token/key pairs.Need something the API does not expose yet? Open a ticket to the Developers department — we read every one.