Run your servers from your own scripts: orders, power, firewalls, snapshots, reverse DNS and invoices. Every endpoint with examples in curl, JavaScript, Python and the rack4 CLI, and a Try it panel that uses your own token.
Every endpoint lives under this URL. Requests and answers are JSON.
shell
https://my.rack4.io/api/v1
Authentication
Send an API token as a bearer token. Make one in the panel at https://my.rack4.io/account/api-tokens (the form asks for your password); it is shown only once, so save it. A read-only token can call every GET endpoint; anything that changes something needs a read-and-write token. A revoked, expired or unknown token gets 401. An account can hold 10 tokens at a time, and making one needs a confirmed email address. A token can be limited to address ranges when it is made; from any other address it gets 403 ip_not_allowed. The examples below assume these two variables.
An error is JSON with a stable code for scripts and a sentence for people. Any endpoint can answer unauthorized (401), insufficient_scope (403, the token lacks the scope), forbidden_role (403, your role on the account can't do this; see Teams), not_found (404, no such endpoint or object), method_not_allowed (405, the path exists but not with that method; the Allow header lists the ones it takes), rate_limited (429) and server_error (500, our fault). Any endpoint that takes a body can also answer invalid_json (400, the body isn't JSON or couldn't be read), body_too_large (413, bodies are 16 KB at most; 64 KB for POST /servers and POST /servers/:id/actions, which can carry a setup script), unsupported_media_type (415, see Conventions) and invalid_request (422, the body isn't a JSON object, has a field we don't know, or a field of the wrong type). Each endpoint below lists its own codes too. A 4xx means the request needs changing before you send it again; only 429, 500 and 503 are worth retrying as they are. A 503 means nothing changed; firewall_unavailable, snapshots_unavailable and orders_unavailable pass quickly, so try again in a moment.
shell
{ "error": { "code": "not_found", "message": "No such server.", "requestId": "req_7d2kq4m5x3n6p2r4s7t2v3w4" } }
Rate limits
Each token gets 60 requests a minute, and each account 120 a minute across all its tokens, so more tokens don't mean more requests. Past either you get 429 rate_limited with a Retry-After header in seconds. Two things have their own limits as well: 10 reverse DNS changes an hour, and 10 orders an hour (tries that fail count, and orders placed in the panel count too). Every answer to a call with a token says where you stand: X-RateLimit-Limit (60, or 120 once the account's limit is the nearer one), X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the minute starts again). GET /prices sends them too, for its own limit.
Request ids
Every answer has an X-Request-Id header (req_ and 24 letters and digits), and an error repeats it as error.requestId. We log each request under its id with the route, the status and the time it took (never the token or the body), so if something looks wrong, quote the id in a support ticket and we can find exactly that request.
shell
{ "error": { "code": "not_found", "message": "No such server.", "requestId": "req_7d2kq4m5x3n6p2r4s7t2v3w4" } }
Conventions
Ids, states, addresses and networks are plain strings. Sizes and amounts are integers: memoryMiB, diskGiB, totalCents. Times are ISO 8601 in UTC. Work that happens on the server answers 202 and finishes in the background: read the server again to follow it. Types are strict. A request with a body must say Content-Type: application/json (charset utf-8 if any; gzip and deflate compression are fine), or it gets 415 unsupported_media_type: curl -d without that header sends a form, which is refused rather than read as empty. The body must be a JSON object; a field we don't know, or a field of the wrong type (a number where a string goes, null, a list), gets 422 invalid_request and nothing changes. Ids are lowercase, exactly as the API gives them out.
Ordering and retries
POST /servers places an order and answers with an invoice; the server is built once that invoice is paid. So that a retry after a timeout can't order twice, send an Idempotency-Key header (up to 60 letters, digits, dots, colons, dashes or underscores; a UUID works) and keep it until you have an answer. The same key sent again with the same order answers with the order it already placed, even if you are now at the limit of unpaid orders or the plan is no longer on sale. The same key with a different order is refused with 422 idempotency_key_reused. Keys are case-sensitive: Order-A and order-a are two keys. Without the header every POST is a new order. Other endpoints ignore the header. Ordering needs a confirmed email address: until then POST /servers answers 403 email_unverified.
Instead of polling, register an HTTPS endpoint (up to 5 per account, on a public address) and we POST each event to it as JSON: { "id", "type", "createdAt", "data" }. Every delivery has the headers Rack4-Event, Rack4-Delivery and Rack4-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of t, a dot and the raw body, keyed with the endpoint's signing secret (shown once when you make the endpoint or rotate it). Check it, and refuse a t more than 5 minutes from your clock. Answer with any 2xx within 10 seconds; anything else is retried after 1 minute, 5 minutes, 30 minutes, 2, 6 and 12 hours. Redirects are not followed. An endpoint that keeps failing for a day is turned off and you are told. The same event can arrive twice (a retry after a lost answer, a redelivery): use its id to skip repeats. Event types: server.ready, server.suspended, server.unsuspended, server.action_failed, snapshot.completed, snapshot.failed, backup.failed, backup.restored, invoice.issued, invoice.paid, invoice.overdue, payment.received, credit.added, ticket.replied, alert.triggered, alert.resolved, monitor.down, monitor.up, monitor.tls_expiring, and ping for a test event. Code samples: https://rack4.io/help/webhooks
Uptime monitors
Monitors check your own services from outside, from our control plane: http (a URL, its status code, an optional keyword, the TLS certificate's expiry), tcp (a host and port), ping (TCP reachability: ports 443, 80 and 22; we can't send ICMP), dns (a record through public resolvers) and heartbeat (your job calls its pingUrl; missed pings mean down). intervalS is 60, 120, 180, 240, 300 seconds (accounts without a server: 300). A failed check is confirmed by a second one 20 seconds later before the monitor is down; then you get one monitor.down notice (in the panel, by email when email is true, and to webhooks subscribed to it) and one monitor.up when it recovers. Only public addresses can be checked: private, loopback, link-local and metadata addresses are refused when you save and before every check. Checks send the user agent Rack4-Monitor/1.0. Status pages show the monitors you pick at https://rack4.io/s/<slug>. Guide: https://rack4.io/help/uptime-monitoring
Notification channels
Channels send the notifications you pick to Slack, Discord, Telegram, ntfy, Microsoft Teams or another email address (up to 10 per account), formatted for each service. categories is any of servers, billing, support, alerts, developer, monitors, deploy; security notices are never sent to a channel. A channel's URL, token, chat or address is kept encrypted and never returned: target shows a masked form. A Telegram channel starts pending with pairing links (open one to connect a chat, within 30 minutes); an email channel starts pending until the address confirms the link we email it. Failed sends are retried on the webhook schedule, and a channel failing for a day is turned off and you are told. Push devices are browsers with push on, added from the panel's notification settings. Guide: https://rack4.io/help/notification-channels
Teams
An account can have several people, each with a role: owner, admin, billing, technical or read only. A token belongs to the person who made it and acts on the account they had open in the panel when they made it; GET /me says which, and their role there. A token never does more than its maker's role allows now: an endpoint the role can't use answers 403 forbidden_role (a role lowered later applies at once), and a read-only member can only make read-only tokens. If the account requires two-factor authentication and the person has it off, every call answers 403 two_factor_required. Removing someone from the account revokes their tokens for it. Ownership transfer is panel-only: it needs the owner's password and 2FA code.
Catalog files
Without the API: https://rack4.io/plans.json has every plan's specs and live price, the add-ons and what every plan includes; https://rack4.io/locations.json lists where servers run. Both are plain JSON for scripts and comparison sites, cached for five minutes, and readable from any origin. A field is only ever added; schemaVersion changes if one is removed or retyped.
AI tools (MCP)
The API is also an MCP server, for Claude, Cursor, VS Code and other AI tools: https://my.rack4.io/mcp over Streamable HTTP with the same bearer token. Every tool call is an API request with the token's scopes, role, address limit and rate limits. A read-only token sees only the read tools; ordering and add-on changes answer with the price until called with confirm: true, and reinstall, delete and snapshot revert or delete also need the server's hostname typed back. Setup for each tool: https://rack4.io/help/mcp.
What is on sale and what it costs, in integer US cents: each plan a month and 12 months at once (yearlyCents; null while a plan has no yearly price), and each add-on per step (step is in its unit: GB, TB, or a count) with its caps per plan. Needs no token (nor does GET /status). 60 requests a minute per address.
What rack4.io/status shows, as JSON: the overall level and each component's (operational, maintenance, degraded, partial_outage or major_outage) with its uptime over 90 days in percent (null before there is data), open incidents and maintenance in progress or scheduled, each with its updates newest first. Needs no token; 60 requests a minute per address; cached for 30 seconds. The same document is at https://rack4.io/status.json.
The person the token belongs to, the account it acts on with their role there (see Teams), and the token itself: its id, name, scopes and expiry (null: never).
Adds a public key (ed25519, ECDSA or RSA of 2048 bits or more). You pick keys when you order a server, and a reinstall puts all of the account's keys on it.
curl -s -X POST -H "Authorization: Bearer $RACK4_TOKEN" -H "Content-Type: application/json" \
-d '{"name":"laptop","publicKey":"your public key here: the one line in ~/.ssh/id_ed25519.pub"}' \
"$API/ssh-keys"
The operating systems you can order or reinstall, in the order form's order. image in POST /servers and in a reinstall takes an id, or a key (the newest image of that system, e.g. ubuntu-24.04). family (debian, rhel or arch, or null) decides which one-click apps an image can take.
The one-click apps a new server or a reinstall can install on first boot: app in POST /servers and in a reinstall takes an id. families lists the image families (GET /images) each runs on, minPlan the smallest plan it runs well on, ports what it listens on publicly, and url its page with the steps it leaves to you.
Your servers, newest first. Deleted ones are left out. label is the name you gave it in the panel (null: none; the hostname is its name) and tags its tags.
Sets the server's display name (label: up to 63 characters, or null for none), its tags (up to 10, each 1 to 24 lowercase letters, digits, dots, dashes, underscores or colons; duplicates are dropped) and private notes (plain text, up to 2,000 characters). Each field you send replaces the stored one; the others are kept. It doesn't change the hostname inside the server: that is set on the first boot (change it there with hostnamectl).
Places an order and answers with the invoice to pay at payUrl; the server is built once it is paid. plan is budget, standard or extreme. You can have 3 unpaid orders at a time and try 10 orders an hour. Send an Idempotency-Key header to make retries safe. Your account's email must be confirmed (403 email_unverified until it is). If orders are paused, this answers 403 staff_only and orders nothing. Optional, installed on first boot: app (a one-click app id such as docker, wordpress or wireguard; it must suit the image's family and the plan), packages (up to 20 distribution package names) and setupScript (up to 16 KiB, run once as root after them; its output goes to /var/log/vps9-setup.log and its exit code to /var/lib/vps9-setup.exit). The script is stored encrypted and never shown back; GET answers only app. term is "month" (the default) or "year": 12 months at once at the yearly price. addons adds resources on top of the plan, in their units: ram_gb, disk_gb (steps of 20), transfer_tb, ipv4 and vcpu, each up to the plan's cap (GET /prices lists them); they are billed for the same term. backups: 1 orders it with off-site backups (priced per plan). restoreFrom (optional): one of your backups (bkp_…), restored into the new server once it is built; the plan's disk (with disk_gb) must be at least the backup's. rootPassword (optional): "generate" makes a random root password and answers with it in rootPassword, this once (it is never stored, so a retry with the same Idempotency-Key can't show it again); or send your own (12 to 128 printable ASCII characters, not a common password). Only its SHA-512 crypt hash reaches the server. With a root password, sshKeyIds may be empty; SSH keys stay the safer way in, and with a key root's password works on the serial console only. promoCode (optional): a promo code; its discount is a line on the invoice. A code that can't be used for this order (unknown, expired, used up, for other plans or yearly billing only, for new customers only) is 422 invalid_promo_code with the reason, and nothing is ordered; 10 wrong codes an hour, with the order form. quantity (optional, 1 to 10, default 1): that many identical servers on one invoice, each with its own order, built once the invoice is paid. Each is named from hostname, numbered: web gives web-1, web-2...; a name ending in a number counts on from it (node09 gives node09, node10); a domain is kept (web-1.example.com). Or, with a quantity over 1, send hostnames, one per server (exactly quantity of them, all different; hostname may then be left out); one server is named by hostname, which it needs. A name that isn't a valid hostname is 422 invalid_hostname naming it. orders in the answer lists every server's order id and hostname (order is the first). A promo code's discount is on the whole invoice; a code that takes a fixed amount off each of the first months is for one server at a time. restoreFrom needs quantity 1. Each order counts once against the 3 unpaid orders, however many servers it has. A retry with the same Idempotency-Key must have the same quantity and names.
Parameters
Idempotency-Keyheader · string
Makes a retry safe: the same key with the same order answers with the order it already placed; with a different order, 422 idempotency_key_reused. Up to 60 letters, digits, dots, colons, dashes or underscores. Case-sensitive.
planbody · string · required
budget, standard or extreme.
imagebody · string · required
An image id or an OS key like ubuntu-24.04 (GET /images).
hostnamebody · string
The server's hostname (needed for one server); with a quantity over 1, the base each server is numbered from (web gives web-1, web-2...), which may be left out when hostnames is sent.
quantitybody · integer
How many identical servers to order on one invoice, 1 to 10. Default 1.
hostnamesbody · list of string
With a quantity over 1 only: one hostname per server (exactly quantity, all different), instead of numbering hostname.
sshKeyIdsbody · list of string · required
At least one key id from GET /ssh-keys.
appbody · string
A one-click app id, like docker.
packagesbody · list of string
setupScriptbody · string
Run once as root on first boot. Up to 16 KiB; stored encrypted, never shown back.
rootPasswordbody · string
"generate" (the answer carries it, once) or your own, 12 or more characters. Left out: no password, SSH keys only. Only its hash reaches the server.
termbody · month | year
Default month.
addonsbody · object
Add-on totals by key, in their units (GB, TB or a count).
promoCodebody · string or null
A promo code, like SPRING25; its discount is a line on the invoice. null or left out: none.
Queues one action: start, stop, reboot, reinstall or delete. A server runs one action at a time. Reinstall wipes the disk and puts your account's SSH keys on it; delete is final. The API doesn't ask you to confirm, so check the id before you send either. A reinstall installs the same image, app, packages and setup script as last time, unless you send image (another image id or key), app (another app, or null for none), packages (replaces them) or setupScript (replaces it; null or "" removes it). A reinstall also takes rootPassword: "generate" (answered once in rootPassword) or your own, as for an order; without it root has no password. Other actions take only action.
Locks the server against deletion: while it is on, delete, reinstall and going back to a snapshot answer 409 protected (in the panel, the API and the CLI). Power actions, snapshots and everything else work as before. Billing and our staff can still act on the server. No body.
Parameters
idpath · string · required
Errors
invalid_state
curl
curl -s -X POST -H "Authorization: Bearer $RACK4_TOKEN" \
"$API/servers/srv_k3j5m2abcdefgh2q/lock"
Unlocks the server. Send your account password, and code (from your authenticator app, or a recovery code) when the account has one. Wrong answers count toward the same limits as the panel's forms. An account that uses passkeys without an authenticator app gets 403 unlock_in_panel: unlock it in the panel, where the passkey can confirm it's you.
The server's power schedule, or null when it has none: a reboot (daily or weekly) and off hours (stopped at stopHour on each of days, started again at startHour, the next morning when it is earlier), in timezone. next has when each runs next (within 5 minutes after the hour); last is the latest run: ok, failed, skipped (with why) or running. heldOff is true while the schedule has the server stopped.
Sets the schedule, replacing any. timezone: an IANA name; reboot: null or { frequency: daily or weekly, hour: 0 to 23, weekday: sun to sat (weekly) }; offHours: null or { stopHour, startHour: 0 to 23, different, days: the weekdays the off hours start on }. At least one of reboot and offHours. A reboot runs only on a running server; the stop is a clean shutdown (forced after a minute); the start at the end of the off hours runs only if the schedule stopped it. A suspended server, one in rescue or one busy for over 30 minutes is skipped until the next time.
The server's add-ons now, and what the next renewal bills (lower where you lowered one), its term, its extra IPv4 addresses (configure them on the server: a reinstall does it for you) and rebootPending: new vCPU or RAM is set and waits for a reboot from the panel or the API (a reboot from inside the server keeps the old size). restartAt: when a smaller size a renewal set is applied by a restart we make (72 hours after the renewal is paid), unless you reboot sooner; null when none is planned.
Sets add-on totals; keys you leave out stay as they are. More is invoiced now for the days left in the paid period (and the next period too when its renewal invoice is already out), at least $0.50, and applied once paid: answer invoice and payUrl. Less takes effect from the next renewal invoice, with no refund (scheduled); once that invoice is issued at the lower size, taking the decrease back waits until it is paid (renewal_issued). Less vCPU or RAM is applied at your next reboot from the panel or the API, or by a restart we make 72 hours after the renewal is paid. A disk never shrinks (disk_shrink). apply is "now" (the default: new vCPU or RAM restarts the server once paid) or "reboot" (it waits for your next reboot). One unpaid add-on invoice per server at a time.
The inbound firewall: the default policy, the rules in order (the first match wins) and whether the latest change is applied. A failed or degraded (partly applied) status comes with a message saying what of the rules shown is in effect. The ETag header (like "r3") changes with every save, not when the status changes; send it back as If-Match on a PUT so you never overwrite a change you haven't seen. This endpoint ignores If-None-Match and always sends the firewall, so you never miss a status change.
Sets the default policy and the whole rule list, up to 50 rules. action is allow or deny; protocol is tcp, udp, icmp or any; ports (tcp and udp only) is one port or a range like 80-443, or null; source is an address or network, or null for anywhere. Replies to connections the server opened always come back in. With If-Match (the ETag from a GET; a weak W/"r3" from a proxy works too), the save happens only if nobody saved since; otherwise it is refused with 412 firewall_changed and nothing changes. If-Match: * saves over any version. Any other If-Match value is refused with 400 invalid_if_match. The answer carries the new ETag.
Parameters
idpath · string · required
If-Matchheader · string
The ETag from GET (like "r3"): save only if nobody saved since, or 412 firewall_changed. * saves over any version.
Takes a disk snapshot. name labels it: up to 40 letters, digits, spaces, dots, dashes or underscores. A running server shuts down for it and starts again, usually within a minute.
The server's automatic snapshot schedule, or null when automatic snapshots are off. nextRunAt is when the next one starts (within 30 minutes after the hour); lastStatus is running, ok, failed or skipped, with the reason in lastError.
Turns automatic snapshots on, or changes the schedule. frequency: daily or weekly; hour: 0 to 23 in timezone (an IANA name, default UTC); weekday (weekly only): 0 to 6 from Sunday, or sun to sat; keep: how many automatic snapshots to keep, 1 to 4 (default 3). Each new one replaces the oldest automatic one. They don't count toward the 3 you take yourself. Like any snapshot, each pauses the server for about a minute. A suspended server, or one in rescue, is skipped until the next run.
The server's off-site backups: whether the add-on is on (and when it ends, if you turned it off), its price for this plan (free: true on a server staff made, which is never billed: on and off at once), the daily hour and time zone, the retention (keepDaily, keepWeekly), the next and last run (lastStatus ok, failed or skipped, with the reason in lastError), the stored size after deduplication and compression, what is queued or running, and the backups, oldest first (sizeBytes: the disk's size; addedBytes: the new data that backup stored). runs: the 10 latest backups, restores and deletes.
Adds the backup add-on (by default 20% of the plan's price): an invoice for the rest of the current period, paid from your credit first. enabled is true once it is paid; otherwise pay it at payUrl. Turning it on again before a scheduled turn-off takes effect cancels the turn-off at no charge. No body.
The daily hour (0 to 23) in timezone (an IANA name), and how many backups to keep: keepDaily (1 to 14, the newest of each day) and keepWeekly (0 to 8, the newest of each week after those). Fields left out keep their value. A run starts within 30 minutes after the hour.
Queues a backup now (3 a day by hand; the schedule's own don't count). It runs when the server's host is free: one backup at a time per host. While it runs the server keeps running and its other actions wait. No body.
Restores the backup over this server: it is shut down, its disk replaced by the backup and started again. Everything written since the backup is lost. To restore into a new server instead, order one with restoreFrom (POST /servers). No body.
The server's rescue: state is off, entering, on, leaving or stuck (switching failed part way: leave rescue to boot the server's own disk). startedAt and expiresAt while it isn't off; available is false while the server is suspended or rescue isn't set up.
Shuts the server down and boots a rescue Linux system, with the server's disk attached as /dev/vdb (not mounted), its IP addresses and its firewall. rootPassword is a one-time root password for the console and SSH: it is in this answer only, so keep it. Your account's SSH keys work too. Rescue ends by itself after 24 hours. A suspended server can't use rescue. 10 an hour. No body.
Asks for a PTR name on one of the server's addresses. The name must already resolve to that address (an A record for IPv4, AAAA for IPv6); the lookup gives up after 3 seconds. The request is pending until it is applied. Ten changes an hour.
Takes the PTR name off the address. A name that was never applied just goes; an applied one shows removing until it is reset. Counts toward the ten changes an hour.
Your invoices, newest first, a page at a time. Amounts are integer cents. limit sets the page size: 50 unless you say, 200 at most. next is null on the last page; otherwise ask for the following page with ?before= and that value, like /invoices?limit=50&before=R4-000042.
Parameters
limitquery · integer
Page size: 1 to 200, 50 by default.
beforequery · string
The next value of the previous page, an invoice number like R4-000042.
Your account credit, what is due in the next 30 days (open invoices plus renewals not invoiced yet) and how much of it credit covers, each billed server's next charge, your open invoices, and what switching a monthly server to yearly would save. upcoming[].state is estimated (no renewal invoice yet: today's prices for the plan and each add-on), invoiced (its renewal is open), paid, or cancelling (deleted at dueAt; nothing more is charged). Credit pays each new invoice first.
Every change to your account credit, newest first, with the balance after it. reason is topup, overpayment (paid over an invoice, or after it closed), refund (to credit), manual (from Rack4 staff), applied (negative: paid an invoice; positive: came back from a voided one) or reversal (a top-up taken back after its payment was reversed). limit: 100 unless you say, 500 at most; more is true when older movements were left out.
Buys account credit: makes a top-up invoice for amountCents (from $5.00 to $1,000.00 unless the limits in GET /billing say otherwise) and a checkout for it. method is btcpay, usdt (USDT on Tron, TRC-20 only) or paypal; left out, the first available. Open checkoutUrl to pay (null when the provider did not answer: pay from payUrl instead). Once paid, the amount is added to your credit, which pays open invoices and renewals first. At most 3 unpaid top-ups at a time (they lapse after 7 days), 10 tries an hour. Needs a confirmed email.
Who your invoices are made out to. Every field is a string, "" when not given; country is an ISO 3166 code like GB. updatedAt is null before you first save.
Sets the whole profile: a field left out is cleared. Fields: name, company, address1, address2, city, region, postalCode (one line of text each), country (an ISO 3166 code or "") and taxId (a VAT number or tax ID; its format is checked for your country and it is stored normalized, like GB123456789; it is not verified with any registry). Invoices issued from now on carry these details; invoices already issued keep the ones they were issued with.
The gift cards your account bought, newest first (100 at most). state is pending (unpaid), active (paid, not redeemed yet), redeemed, void or lapsed (never paid). The code itself is never in the API: it is on the card's page in the panel and in the recipient's email; hint is its last four characters.
Makes a gift card and the invoice for it, with a checkout. amountCents is one of 1000, 2500, 5000, 10000 or 20000; recipientEmail is where the card is emailed once the invoice is paid; recipientName, fromName (up to 80 characters each) and message (up to 500) are optional and appear on the card. method is btcpay, usdt (USDT on Tron, TRC-20 only) or paypal; left out, the first available. Account credit can't pay a gift card. At most 3 unpaid gift cards at a time (they lapse after 7 days), 10 tries an hour. Needs a confirmed email.
Adds a gift card's whole amount to your account credit, which then pays your open invoices first. code is as printed (GIFT-XXXX-XXXX-XXXX-XXXX), in any case, with or without dashes. A code works once. 10 wrong codes an hour per account (and per address); a right one doesn't count. Needs a confirmed email.
Your notifications, newest first, 50 a page (page=2 for the next; ten pages at most), the unread count, and whether there are more. unread=true lists only unread ones. Kept 90 days, 500 at most.
metric: cpu, memory, transfer (this month's traffic against the allowance) or down (the host hasn't reported the server, or it used no CPU, for durationMin). threshold: a whole percentage from 1 to 100 (not for down). durationMin: 5, 10, 15, 30 or 60 (not for transfer). email: also email you (default true). Checked every minute; one notice when it fires and one when it clears, then quiet for 30 minutes. 10 rules per server.
Send any of enabled, email, threshold and durationMin. A change ends an incident in progress quietly; the rule is judged afresh from the next minute. The metric can't change.
url: https only, on a public address (private, loopback, link-local and metadata addresses are refused, and checked again before every delivery). events: the types to send. The answer carries the signing secret: the only time it is shown.
A new secret, in the answer only. For 24 hours each delivery carries a second v1 signature made with the old one, so your receiver can switch over without dropping events.
Parameters
idpath · string · required
curl
curl -s -X POST -H "Authorization: Bearer $RACK4_TOKEN" \
"$API/webhooks/whk_x7m2q4abcdefgh3c/rotate-secret"
Your monitors, oldest first, each with uptime over 24 hours and 30 days, the average response time over 24 hours and hourly averages (spark). limits: how many you may have, and the shortest interval.
kind: http, tcp, ping, dns or heartbeat; name. http: url, and optionally method (GET, HEAD, POST, PUT), statusMin and statusMax (default 200-399), keyword with keywordAbsent, followRedirects (default true), body (POST and PUT, at most 1 KiB), tlsWarnDays (0, 7, 14, 21 or 30). tcp: host and port. ping: host. dns: host, dnsType (A, AAAA, CNAME, MX, TXT, NS) and optionally dnsExpect. heartbeat: periodS (60, 300, 600, 900, 1800, 3600, 7200, 21600, 43200, 86400, 604800) and graceS (60, 300, 600, 1800, 3600, 7200); the answer carries its pingUrl. Others: intervalS, timeoutS (1 to 30), email (default true). The first check runs within seconds.
One monitor with its uptime over 24 hours, 7, 30 and 90 days, its response time (range=24h, 7d, 30d or 90d; average, slowest and the series, oldest first) and its last 90 days.
Send any of the fields you can add a monitor with, except kind. A change to what is checked starts the monitor afresh (pending), and ends an outage in progress without a notice.
The monitor's checks of the last 48 hours, newest first (500 at most): passed or not, the response time, the status code and why it failed. For a heartbeat, its pings.
slug (3 to 40 lowercase letters, digits and dashes: the page is at https://rack4.io/s/<slug>), title, and optionally logoText, about, monitorIds (up to 20, in display order) and indexable (default false: the page asks search engines not to list it). The page shows names, states and uptime, never what a monitor checks.
kind: incident, maintenance or notice; title (120 characters) and body (2000). Open notes show at the top of the page; an open incident marks the page as partly down.
curl -s -X POST -H "Authorization: Bearer $RACK4_TOKEN" -H "Content-Type: application/json" \
-d '{"kind":"maintenance","title":"Database upgrade","body":"Sunday 02:00 to 02:30 UTC. Checkout may be slow."}' \
"$API/status-pages/spg_m2k7q4abcdefgh4d/notes"
answer 201
{
"id": "spn_x4q7m2abcdefgh4e",
"kind": "maintenance",
"title": "Database upgrade",
"body": "Sunday 02:00 to 02:30 UTC. Checkout may be slow.",
"createdAt": "2026-10-21T09:00:00.000Z",
"resolvedAt": null
}
kind: slack, discord, teams or ntfy (send url: an incoming webhook, a channel webhook, a Teams Workflows URL, or an ntfy topic URL on any public server, with an optional token for a protected topic), telegram (no url; the answer has the pairing links), or email (send email; it gets a confirmation link first). name is optional. Slack, Discord and Teams URLs must be on their service's own hosts; every URL must be https on a public address.
Queues a test message to the channel now (one attempt, whatever its categories); 5 a minute per account. The channel must be on and confirmed or paired.
Parameters
idpath · string · required
Errors
rate_limitedchannel_not_ready
curl
curl -s -X POST -H "Authorization: Bearer $RACK4_TOKEN" \
"$API/channels/chn_s4k7q2abcdefgh3f/test"
Servers you deleted that are still kept: state deleted, purgeAt when the disk is wiped for good. Their addresses stay reserved for them until then. restore says whether it can be restored now, and the open invoice of a restore already ordered.
Orders the server back, with its disk and its addresses: an invoice for a new billing period (term month or year, at today's prices, with the add-ons it had), paid like any order; account credit applies. Once it is paid the server boots again, billed from that day. Asking again while that invoice is unpaid answers the same invoice.
Your own account's AI assistant setting. mode: on (answers questions in the panel and adds a short explanation to alert and failure notifications), on_demand (only when someone asks) or off. available: whether the assistant runs right now; questionsLeftToday and dailyLimit: your account's questions (UTC day).
Opens a ticket and emails you a receipt. category is technical, billing, sales, abuse, account or other (other when left out); priority is low, normal (the default), high or urgent; serverId (optional) links one of your servers. Plain text; put logs between lines of ``` to keep their layout. 5 new tickets an hour and 20 a day per account.
curl -s -X POST -H "Authorization: Bearer $RACK4_TOKEN" -H "Content-Type: application/json" \
-d '{"subject":"web-1 doesn't answer on port 443","category":"technical","priority":"high","serverId":"srv_k3j5m2abcdefgh2q","body":"Nginx is running and ufw allows 443, but curl from outside times out."}' \
"$API/tickets"
answer 201
{
"id": "tkt_m4q2x7abcdefgh2u",
"subject": "web-1 doesn't answer on port 443",
"category": "technical",
"priority": "high",
"status": "open",
"serverId": "srv_k3j5m2abcdefgh2q",
"createdAt": "2026-10-12T09:00:00.000Z",
"updatedAt": "2026-10-12T09:00:00.000Z",
"closedAt": null,
"rating": null,
"messages": [
{
"id": 41,
"at": "2026-10-12T09:00:00.000Z",
"from": "you",
"body": "Nginx is running and ufw allows 443, but curl from outside times out."
}
]
}
Emails an invite link that works once, for 7 days, for exactly this address. role: admin, billing, technical or readonly. Owners and admins only, with a confirmed email (403 email_unverified until then); 20 invites an hour per account.