# RareCloud > RareCloud is an AI-first European cloud provider. We run our own OpenStack > platform in the EU (Bucharest, Romania) and offer a Cloud VM billed by the > hour with an automatic monthly cap, managed Kubernetes, block storage, load > balancers and reserved IPs, all paid from a credit balance. Alongside the > cloud we run classic, fixed-term hosting: KVM and Windows VPS, dedicated > servers across 12 datacenters, and cPanel web hosting, plus residential and > mobile proxies and domain registration. Everything is driven by a public > REST API (https://api.rarecloud.io/v1) with scoped bearer tokens, and we > ship an MCP server, a CLI and a Terraform provider so developers and AI > agents can manage infrastructure programmatically. EU data, EU jurisdiction, > real EU VAT invoices. Pay by card, PayPal, crypto or bank transfer. ## Cloud (own OpenStack, billed hourly from a credit balance) - [Cloud VM](https://rarecloud.io/cloud-vm/): Cloud servers in the EU, deploy in about 30 seconds, billed by the hour with an automatic monthly cap, from EUR 3.99/month, in EUR or USD. - [Managed Kubernetes](https://rarecloud.io/kubernetes/): Upstream Kubernetes managed by Gardener. You pay only for worker nodes at Cloud VM prices; optional high-availability control plane add-on at EUR 30/month. - [Block Storage](https://rarecloud.io/block-storage/): Attachable block volumes for cloud servers at EUR 0.05 per GB per month, billed hourly, managed from the console or API. - [Load Balancers](https://rarecloud.io/load-balancer/): Managed L4 load balancers with health checks and a public IP included, from EUR 7.30/month, billed hourly. - [Object Storage](https://rarecloud.io/object-storage/): S3-compatible buckets in the EU, EUR 10/month including 1 TB and egress up to your stored volume, then EUR 0.01/GB-month, billed hourly. Works with aws-cli, rclone, s3cmd, mc and every S3 SDK. ## Classic hosting (fixed-term, prepaid plans) - [KVM VPS](https://rarecloud.io/kvm-vps/): Linux KVM virtual servers with NVMe storage, full root access and instant activation. - [Windows VPS](https://rarecloud.io/windows-vps/): Windows RDP servers with full admin access, fast NVMe storage and 1 Gbps connectivity. - [Dedicated Servers](https://rarecloud.io/dedicated-servers/): Bare-metal Intel Xeon servers with full root access across 12 datacenters. - [Web Hosting](https://rarecloud.io/web-hosting/): cPanel web hosting with NVMe, free SSL and automated backups. - [N8N VPS](https://rarecloud.io/n8n-vps/): Pre-installed n8n automation platform with PostgreSQL, Redis and SSL. - [OpenClaw VPS](https://rarecloud.io/openclaw-vps/): Pre-configured, security-hardened self-hosted AI assistant. ## Proxies - [Static Residential Proxies (ISP)](https://rarecloud.io/residential-proxy/): ISP-assigned static residential IPs, HTTP/HTTPS and SOCKS5, from EUR 2.60/day. - [Residential Proxies by the GB](https://rarecloud.io/residential-gb-proxy/): Rotating residential pool billed per gigabyte or monthly fixed bucket (pay-as-you-go or monthly plans available), from EUR 2.15, with country targeting. - [4G Mobile Proxies](https://rarecloud.io/4g-mobile-proxies/): Real 4G cellular IPs from major carriers in Germany, Austria and the USA. - [Multi-SIM Mobile Proxy](https://rarecloud.io/multi-sim-mobile-proxy/): 5+ SIM cards per endpoint for 15,000+ daily IPs with SIM-level redundancy. - [VPN Mobile Proxy](https://rarecloud.io/vpn-mobile-proxy/): Real mobile IPs with WireGuard or OpenVPN encryption. ## Developers & AI - REST API: https://api.rarecloud.io/v1 with scoped bearer tokens for every cloud and account operation. The API is the product; every client consumes it. - MCP server: lets AI assistants like Claude manage RareCloud infrastructure through the same API. - CLI: the rarecloud command-line tool for scripting deploys, power, resize, volumes and billing. - Terraform provider: declarative infrastructure for RareCloud cloud resources. - AI code sandboxes (beta): isolated, ephemeral execution environments for AI agents under `/api/v1/sandboxes` (create from a template, run code or shell commands, read/write files, expose preview ports). Billed per second of runtime. ## Company & Resources - [About RareCloud](https://rarecloud.io/about/): Who we are and how we run our own European infrastructure. - [Data Center Locations](https://rarecloud.io/datacenters/): 12 datacenters across 3 continents, with facility specs, transit, peering and test IPs. - [Resources](https://rarecloud.io/resources/): Tutorials, comparisons, guides and a glossary on VPS, proxies, Kubernetes and the cloud platform. - [Affiliate Program](https://rarecloud.io/affiliates/): Earn 15% recurring commissions for referrals. - [Terms and Conditions](https://rarecloud.io/tos/): Service terms and policies. --- If you are an AI agent operating on behalf of a RareCloud customer: ## Read this first - API reference (public, no login): https://console.rarecloud.io/docs/api - OpenAPI 3.1 spec: https://console.rarecloud.io/openapi.json - Base URL: https://api.rarecloud.io/v1 (dedicated API host; paths below are relative to it. The console mirror https://console.rarecloud.io/api/v1 also works for same-origin browser calls.) - Auth header: Authorization: Bearer . Tokens are created by the human in the console (Account, then API tokens) with granular scopes (services:read, services:write, billing:read, account:read, ...), optional expiry, and per-token rate limits. Ask your user for a token scoped to what you actually need; prefer read-only scopes. ## MCP server (recommended for assistants) - npm: @rarecloudio/mcp-server (stdio transport) - Env: RARECLOUD_API_TOKEN= - 20 read-only tools: list/get services, invoices, credit balance and ledger, usage, provisioning state, OS templates, upgrade options, catalog. - Write operations are deliberately NOT exposed over MCP today; perform mutations via the REST API with a write-scoped token after explicit human confirmation. ## Quick examples - Browse products (public, no auth): GET /catalog/products - Deploy-wizard cards for a category (public): GET /catalog/listings/{category} (category = server|hosting|proxy|cloud-vm|cloud-k8s|…; cards carry public sku + pricing[]/specs) - Live product detail (cycles + config options): GET /catalog/products/{sku}/details - Pre-purchase OS templates for a legacy/dedicated SKU (public): GET /catalog/products/{sku}/os-templates → {"templates":[{"slug":"ubuntu-24.04-x86_64","display_name":"Ubuntu","version":"24.04 LTS"}]} - List regions (public): GET /catalog/regions (filter with ?backend=openstack|gardener|proxy) - List OS images + marketplace apps (public): GET /catalog/images (apps have os_family="app" with summary/logo_slug) - Managed-K8s versions on offer (public): GET /catalog/kubernetes-versions → {"versions":["1.32.0",...]} - List services: GET /services/ - Suspended services: Service.suspendedBy is "billing" (top up to resume) or "admin" (abuse/legal — Service.suspendedReason says why; contact support). - Deploy a cloud server: POST /services/ with JSON {"category":"cloud-vm","productId":"g-2vcpu-8gb","region":"bucharest-ro", "imageId":"ubuntu-24.04","hostname":"web-01","sshPublicKey":"ssh-ed25519 ..."} Response data: {serviceId, status, publicIp?, consolePassword?}. consolePassword is an auto-generated CONSOLE-ONLY (VNC) root password returned ONCE when you deploy with an SSH key (or no auth) and do NOT pass rootPassword — save it, it is shown only once, works only on the web/VNC console, and NOT over SSH. If you pass rootPassword WITHOUT an SSH key, no consolePassword is returned and your password works on both the console AND over SSH (root password login is enabled on the VM). If you pass rootPassword together with an SSH key, SSH stays key-only and your password works on the console only. - Deploy a legacy product (server/hosting/proxy): POST /services/ with {"category":"server","productId":"...","billingCycle":"monthly","hostname":"box-01", "imageId":"ubuntu-24.04","rootPassword":"...","configOptions":{"":""}, "customFields":{"":""}} (configOptions + customFields are forwarded verbatim to the WHMCS order). A legacy VPS (category "server") REQUIRES a hostname and an auth credential (rootPassword OR an SSH key), and — when the product exposes an Operating System field — an OS via imageId (mapped to the product's OS field) or customFields; otherwise the order is rejected with INVALID_PARAM ("A hostname is required" / "An SSH key or a root password is required" / "Choose an operating system."). Config-option values are validated against the product's details; an option marked "disabled" there (a retired location, e.g. Bucharest for legacy VPS: it is moving to RareCloud Cloud) is refused with INVALID_PARAM carrying its disabledReason. - List your orders: GET /orders (purchase records; create via POST /services) - Get one order: GET /orders/{id} (your own only; cross-user → 404) - Check domain availability: GET /domains/availability?domain=example.com (scope domains:read → {domain, available, premium?}) - TLD pricing: GET /domains/tld-pricing (register/transfer/renew per TLD) - List your domains: GET /domains (your own only) - Get one domain: GET /domains/{id} (your own only; cross-user → 404) - Register a domain: POST /domains with {"domain":"example.com","years":1,"nameservers":["ns1...","ns2..."]} (order-driven → {orderId, invoiceId, status:"Pending"}; registrar fulfils on pay) - Transfer a domain in: POST /domains/transfers with {"domain":"example.com","epp":"","years":1} - Renew a domain: POST /domains/{id}/renew with {"years":1,"autoRenew":true} - Renew a legacy service (VPS/hosting/legacy proxy): POST /services/{id}/renew (returns {invoiceId, created}: the existing Unpaid renewal invoice, or a new one created inside the early-renew window; pay it from balance via POST /billing/invoices/{id}/pay or by card via the checkout URL. ACE proxies renew via POST /proxies/{id}/renew instead) - Mobile proxy order prices: GET /proxies/mobile/prices → {currency, prices:[{kind, location, monthlyCents, oneDayCents}]}, what ordering a mobile proxy bills per line in your billing currency (a full plan = monthlyCents × months × quantity; a 1-Day test = oneDayCents; monthlyCents is null while that product cannot be priced) - Mobile 1-Day test + upgrade: order a mobile proxy with {"kind":"mobile-4g","term":"test24h"} (quantity forced to 1, no months) for a 1-Day full-proxy test; upgrade it in place to a full 1/3/6/12-month plan via POST /proxies/{id}/renew {"months":3} (keeps the same credentials; charged the full-plan price, the test payment is not credited) - Reactivate a lapsed proxy (MOBILE-ONLY): an expired/cancelled MOBILE service can still be renewed via POST /proxies/{id}/renew for 14 days after expiry (same line, new expiry counted from now). A lapsed ISP/GB residential service cannot be renewed — order a new one. The service object carries `reactivatable` (boolean) and `reactivationDaysLeft` (14…1, or null when not applicable — active/PAYG/1-Day test/non-mobile kind/past the window). Past the window or for non-mobile kinds, renew returns 409 REACTIVATION_WINDOW_CLOSED. - Auto-renew from balance, per service: GET/PUT /services/{id}/autorenew {"enabled":false} to OPT OUT. Default (no setting) = AUTO: at the due date every renewal invoice is paid automatically from bonus first, then credit; cancel before the due date to let a service lapse. ACE proxies excluded. (order-driven → {orderId, invoiceId, status:"Pending"}; charged, registrar fulfils on pay) - Download VPN config file (mobile-vpn proxy lines only): GET /proxies/{id}/vpn-config Returns the OpenVPN (.ovpn) or WireGuard (.conf) connect file as plain text; Content-Disposition header provides the filename for download. - Proxy service objects (GET /proxies, GET /proxies/{id}) include an optional `reference` string — an opaque order reference for support/cross-referencing a service upstream. Null when no order id was recorded. Never the service code. - Proxy service objects also carry `port` (integer|null): the cached vendor port for mobile lines, so a specific line can be identified without opening its proxy-list. Null when not yet cached (it backfills daily server-side). - Nameservers: GET /domains/{id}/nameservers; PUT with {"nameservers":["ns1...","ns2..."]} (2–5) - Registrant contact: GET /domains/{id}/contacts; PUT with the DomainContact fields - DNS host records: GET /domains/{id}/dns; PUT with {"records":[{"hostname":"www","type":"CNAME","address":"example.com."}]} (registrar-dependent → 501 NOT_IMPLEMENTED when unsupported) - Domain manage snapshot/actions: GET /domains/{id}/manage (combined state: status/expiry/autoRenew/idProtection/nameservers/locked; per-registrar nameservers+locked may be null); POST with {"action":"nameservers|lock|autorenew|idprotect|epp", ...} - Managed-K8s kubeconfig: GET /services/{id}/kubeconfig (short-lived admin kubeconfig; your own cluster only → {kubeconfig, expiresAt}) - Managed-K8s long-lived kubeconfigs: POST /services/{id}/kubeconfigs {name, role: admin|view, ttl: 30d|90d|1y|never} → credential + kubeconfig; GET /services/{id}/kubeconfigs (list); GET .../kubeconfigs/{credId}/download; DELETE .../kubeconfigs/{credId} (revoke — instant, deletes the ServiceAccount) - Cluster scale (read): GET /services/{id}/scale (worker pools + add-ons) - Cluster scale (set): POST /services/{id}/scale with {"minimum":2,"maximum":5} (first worker pool; integers, min>=1 and max>=min; your own cluster only) - Enable HA control plane: POST /services/{id}/high-availability (no body; add-only, idempotent — once enabled cannot be removed; adds €30/mo billed hourly; your own cluster only → 404 otherwise) - Node pools (managed-K8s): create with up to 5 named pools via POST /services {"category":"cloud-k8s","productId":...,"pools":[{"name":"workers","minimum":1,"maximum":3},...]}; manage later — GET /services/{id}/pools (list), POST /services/{id}/pools (add {name,machineType?,minimum,maximum,volumeSizeGb?}), PATCH /services/{id}/pools/{pool} (edit min/max/machineType/volume), POST /services/{id}/pools/{pool}/rename {"name":...} (rolling node replacement), DELETE /services/{id}/pools/{pool} (never the last; machineType resolved server-side; your own cluster only → 404 otherwise) - Resize a cloud VM: POST /services/{id}/resize with {"flavor":"c-4vcpu-8gb"} (target plan PUBLIC SKU, not a raw flavor id — mapped server-side; async → 202 {status:"resizing"}, then poll GET /services/{id} until ACTIVE; no disk shrink; billing follows the new plan; your own VM only → 404 otherwise) - Reverse DNS for a cloud VM's primary IPv4: GET /services/{id}/rdns reads the current PTR; PUT /services/{id}/rdns with {"hostname":"mail.example.com"} replaces it; DELETE /services/{id}/rdns removes it. The API derives the IP from your owned VM and never accepts an arbitrary address. Only configured RareCloud reverse zones are writable; another account's VM returns 404. - Reinstall a cloud VM (destructive rebuild, keeps the IP): POST /services/{id}/actions/reinstall { "imageId": "ubuntu-24.04", "sshPublicKey": "ssh-ed25519 ..." } imageId is required: a curated slug, or the id of one of YOUR OWN snapshots (a snapshot of a different VM is allowed) - both come from GET /services/{id}/os-templates, whose entries carry kind: "stock" | "snapshot". A snapshot is refused if it needs a bigger disk than this VM has; the error names both sizes. Provide password OR sshPublicKey, or neither for an auto-generated console (VNC) password returned ONCE as data.consolePassword. Async: poll GET /services/{id} until status=active. Your own VM only (else 404). Password/key semantics match deploy: a password without a key enables SSH password login; with a key, SSH stays key-only. - Reset the root password on a RUNNING cloud VM (non-destructive, no reboot, data intact): POST /services/{id}/actions/reset-password { "password": "8-128 chars" } Applied live via Nova os-set-password + qemu-guest-agent; returns data.status="reset-requested" (Nova accepted it; the guest applies it within ~1 min). Only available when GET /services/{id} reports capabilities.resetRootPassword=true (the VM booted with the guest-agent channel; older VMs must Reinstall first). The VM must be running (409 otherwise). Your own VM only (else 404). The new password always works on the VNC console; over SSH only on VMs deployed/reinstalled with password authentication (SSH-key VMs stay key-only). - Cloud-VM resource-usage graphs (live CPU/memory/disk/network): GET /services/{id}/metrics?range=1h|24h|7d Returns data = { range, step, series{cpu_pct, mem_used_pct, mem_total_bytes, disk_read_bps, disk_write_bps, net_rx_bps, net_tx_bps}, latest, unavailable }; each series is an array of [unixSeconds, value|null]. Scope services:read; your own VM only (else 404). unavailable=true (empty series) if the metrics store is briefly unreachable. Legacy VPS (numeric id) return a point-in-time snapshot instead (cpu_util_pct, ram_used_mb, …). - Per-VPS SSH-key library (legacy Virtualizor) — list: GET /services/{id}/ssh-keys/library (each key has id, name, publicKey, fingerprint computed server-side; your own service only → 404 otherwise; distinct from the gateway GET /services/{id}/ssh-keys) - Add a key to the library: POST /services/{id}/ssh-keys/library with {"name":"laptop","key":"ssh-ed25519 AAAA..."} - Apply a selected SET of library keys to the VPS: POST /services/{id}/ssh-keys/library/apply with {"keyIds":["7","9"]} - cPanel hosting (category="hosting") usage + real connection facts: GET /services/{id} returns data.hosting = { username, serverHostname?, diskUsedMb?, diskLimitMb?, bwUsedMb?, bwLimitMb? } when the cPanel provisioning module reports a username (account provisioned). MB units are WHMCS-native. Individual usage fields are OMITTED (not zeroed) when WHMCS didn't report them — never guess. FTP/SSH ports are intentionally NOT returned (shared-hosting shell access isn't guaranteed; ports 21/22 are not assumed real). To manage email/databases/files/FTP, mint a single-use SSO URL into the WHMCS product page (which surfaces the native "Login to cPanel" action): POST /services/{id}/panel-sso → {"url":...}. To deep-link straight into a specific cPanel app instead (skipping the WHMCS product page), use POST /services/{id}/cpanel-sso with an optional body {"app":"Email_Accounts"|"FileManager_Home"} → {"url":...}. Omit "app" for a plain cPanel-home login. Hosting (category="hosting") services only — a VPS/proxy id returns INVALID_PARAM. Any "app" value outside the two listed is rejected (INVALID_PARAM). - List block volumes: GET /volumes/ - Create a 50 GB volume: POST /volumes/ with {"sizeGb":50,"name":"data"} - Attach a volume to a VM: POST /volumes/{id}/attach with {"serverId":""} - Enable object storage: POST /object-storage (idempotent; returns the account with its endpoint and price card) - Create a bucket: POST /object-storage/buckets {"name":"media","region":"eu-central-1","versioning":false} - Create an access key: POST /object-storage/keys {"name":"ci","scope":{"buckets":"*","access":"readwrite"}} → the response carries secretAccessKey ONCE; it is never retrievable again. - Use it: aws --endpoint-url https://s3.eu-central-1.rarestack.net s3 ls s3:// - Delete a bucket: DELETE /object-storage/buckets/{id}?confirm=[&purge=true] (confirm must equal the bucket name; without purge a non-empty bucket is refused with CONFLICT) - The container registry is NOT launched yet. Until it is, every registry write below (enable, tier change, close, credentials, cluster link/unlink/rotate, repository/tag/manifest deletion, the Kubernetes manifest) answers 503 BACKEND_UNAVAILABLE and changes nothing, the token endpoint issues no tokens (docker login fails), and POST /services with registry:{link:true} still creates the cluster but reports registry.reason "not_available". Reads of an account that already exists keep working. - List registry tiers and prices: GET /registry/tiers → {"items":[{"tier":"free","quotaGb":10, "burstCeilingGb":10,"monthlyCents":{"EUR":0,"USD":0},"overageCentsPerGbMonth":2,"available":false}, ...]} Unauthenticated callers see ONLY tiers with available:true (none, pre-launch); a signed-in caller sees all four so the console's own tier picker can price them before the public catalog flips. - Suggest a free handle: GET /registry/handles/suggest?base=acme → {"suggestions":["acme42","acme07","acme81"]} (up to 3, always free/valid/never reserved; base is optional and free-form) - Enable the container registry: POST /registry {"handle":"acme","tier":"free"} (handle is customer-chosen and immutable; it becomes your image path prefix registry.rarestack.net/acme/) - Create a push credential: POST /registry/credentials {"name":"ci","scope":"push"} → username "acme+ci", secret shown ONCE in the response. - docker login registry.rarestack.net -u acme+ci - docker push registry.rarestack.net/acme/app:1.0 - List registry credentials: GET /registry/credentials (never a secret); revoke one: DELETE /registry/credentials/{id} The token endpoint (GET|POST /registry/token) is called automatically by Docker/OCI clients on login/push/pull — you never call it by hand. - GET /registry also returns usage {logicalBytes,repoCount,sampledAt} (null until first measured), quota {quotaGb,burstCeilingGb,overageCentsPerGbMonth}, and push {allowed,reason}, reason explains why a push would currently be refused (over_ceiling, balance_under_72h, meter_stale_24h, repo_limit, ...) before you try one. repo_limit means new repositories are refused (you are at your tier's repository count cap); pushes to a repository you already have keep working. - Change tier: PATCH /registry {"tier":"starter"} (upgrades always allowed; a downgrade below your current measured usage is refused with CONFLICT tier_below_usage) - Close the registry: DELETE /registry?confirm= → 202 {"status":"closed","deleteAfter":...}. Storage is NOT deleted immediately, POST /registry with the SAME handle reopens it before deleteAfter. Past deleteAfter the storage is purged for good, but the account is not lost: POST /registry still works (200) and RE-ENABLES it, reusing the same handle (reclaimed) or, if you name a different one, claiming it as long as it is currently free. The tier in that request applies fresh; nothing carries over from before the close. - List repositories: GET /registry/repositories (paginated: limit/cursor) - One repository + its tags: GET /registry/repositories/{repo} (each tag carries CVE severity counts) - Delete an entire repository: DELETE /registry/repositories/{repo} - Delete one tag only (the image and any other tag on it are untouched): DELETE /registry/repositories/{repo}/tags/{tag} - Delete an image by digest (untags every tag pointing at it, pass ?confirm=true if more than one does): DELETE /registry/repositories/{repo}/manifests/{digest} - CVE findings for one tag: GET /registry/repositories/{repo}/tags/{tag}/vulnerabilities (paginated) - Link a Kubernetes cluster you own to the registry: POST /registry/clusters/{serviceId} → 202 {"link":{"serviceId":...,"status":"linking","credentialName":"k8s-", "secretName":"rarecloud-registry-credentials","installs":[...]},"disclosure":"..."} Installs a small controller (rarecloud-registry-syncer) with cluster-wide Secret/ServiceAccount write access, fanning the pull secret out to every present and future namespace except kube-*/gardener*/monitoring and anything labelled rarecloud.io/registry-secret=disabled; 409 already_linked on a repeat call; unreachable cluster stays "linking", the hourly reconciler finishes it. - List linked clusters: GET /registry/clusters → {"items":[{serviceId,clusterName,status, installedAt,syncerImage,syncerReady,syncerCheckedAt,lastError,credentialName}]} (credentialName is the pull credential's name, e.g. "k8s-", never the secret itself; null before the first successful link) - Rotate a linked cluster's pull credential: POST /registry/clusters/{serviceId}/rotate → 202 {"status":"active"|"rotating","credentialName":...}; 409 rotation_in_progress on a second call while one is running; 503 cluster_unreachable leaves the OLD credential still active. - Unlink a cluster (removes everything the syncer installed, revokes the credential): DELETE /registry/clusters/{serviceId} → 202 {"status":"unlinking"|"unlinked"} (idempotent: a repeat call while still unlinking is a no-op 202). - Prompt at cloud-k8s create time: POST /services {"category":"cloud-k8s",..., "registry":{"link":true}} → the response echoes registry:{"linked":true,"status":"linking"} (or {"linked":false,"reason":"not_enabled"|"link_failed"|"not_available"}; not_available means the registry is not launched yet, no link was recorded, the cluster itself is still created). - A cluster you manage yourself (BYO, not linked): POST /registry/docker-credentials/kubernetes (query: scope=pull|push, expiry=30d|90d|1y|never, namespace, secretName) → a ready-to-apply Secret manifest (kubectl apply -f), or Accept: application/json for the same credential and manifest as structured JSON. Same Secret shape the syncer installs, just unmanaged: apply and rotate it yourself. Not launched yet: the catalog stays hidden (GET /registry/tiers reports available:false for every tier to a caller who is not signed in, until the catalog flips), so prices are not published beyond a signed-in customer, and every registry write answers 503 until that same launch. A flag-gated console page (Container Registry) now covers enable, overview, credentials, repositories, tier change and close for accounts with the launch flag on. Kubernetes cluster linking also has console coverage: the cluster-create wizard shows a checked-by-default "Link the container registry" step (the same POST /services registry:{link} field above) when the account has a registry, or a short inline hint when it does not; every cluster's own page also carries a registry card (status, syncer readiness, credential name, Unlink/Rotate with confirm) regardless of how it was linked. - List your snapshots: GET /snapshots/ - Snapshots of one cloud VM: GET /services/{id}/snapshots/ - Take a snapshot: POST /services/{id}/snapshots/ with {"name":"before-upgrade"} Cloud VMs only (a Virtualizor VPS id returns NOT_FOUND; legacy VPS backups live in the Virtualizor panel). The VM keeps running. It returns immediately: the snapshot is usable within seconds ("securing") and reaches "ready" once fully independent. Up to 5 per VM. "usedBytes" is null until measured, and that measured figure is what is billed. - Restore a VM from its own snapshot: POST /services/{id}/restore/ with {"snapshotId":""} DESTRUCTIVE — the disk is replaced and everything written since is lost. The snapshot must be one taken from that same VM. - Delete a snapshot: DELETE /snapshots/{id} — blocked (CONFLICT) while VMs created from it still exist; the error names them. Deleting a VM or reinstalling it is blocked while one of its snapshots is still securing. - List load balancers: GET /load-balancers/ - Create an L4 LB: POST /load-balancers/ with {"name":"web-lb","port":80,"memberServerIds":[""]} - List LB members: GET /load-balancers/{id}/members/ - List VPCs: GET /networks/ - Create a VPC: POST /networks/ with {"name":"staging"} - Move a VM into a VPC: POST /networks/{id}/vms with {"serverId":""} - List firewalls: GET /firewalls/ (each: id, name, isDefault, inboundCount, outboundCount, attachedServerIds; one "default" firewall is the per-account baseline applied to every VM — editable, not deletable) - Create a firewall: POST /firewalls/ with {"name":"web-fw"} (seeded with the default inbound rules: icmp + tcp 22/80/443; outbound open by default) - Get one (rules + attachments): GET /firewalls/{id} → {inbound[], outbound[], attachedServerIds} - Add a rule: POST /firewalls/{id}/rules with {"direction":"inbound","protocol":"tcp","portRangeMin":2310,"portRangeMax":2310,"remoteCidr":"0.0.0.0/0"} (direction inbound|outbound; protocol tcp|udp|icmp|all; ports omitted for icmp/all) - Remove a rule: DELETE /firewalls/{id}/rules/{ruleId} - Attach a firewall to a VM: POST /firewalls/{id}/attach with {"serverId":""} - Detach: POST /firewalls/{id}/detach with {"serverId":""} - Delete a firewall: DELETE /firewalls/{id} (the default firewall, or one still attached to a VM, can't be deleted → CONFLICT) - Credit balance: GET /billing/credit/ - Account profile: GET /account (includes optional taxRatePercent: the VAT % applied to this client's invoices, 0 = tax-exempt/reverse-charge; prices are NET, so total = price * (1 + taxRatePercent/100). Absent = rate unknown.) - Update account profile: PATCH /account with only the fields to change (firstName, lastName, companyName, address, city, postcode, country, phone, taxId, language, email). `email` updates the billing/contact email; the login email stays the same (contact support to change it). - Payment options: GET /billing/payment-methods (WHMCS gateways; no stored-card vault) - Active credit campaign (banner): GET /billing/campaign (or {campaign:null}) - Hosted pay link for an invoice: POST /billing/invoices/{id}/checkout-url (returns a logged-in URL; only works for your own invoice) - Pay-from-balance preview: GET /billing/invoices/{id}/pay-preview (read-only: how much promo bonus + real credit a pay-from-balance would use, and any shortfall; consumes nothing). Pay it: POST /billing/invoices/{id}/pay {"payWith":"credit"} — bonus is always consumed before real credit. - Two-factor status: GET /account/two-factor (your own account only) - Manage two-factor: POST /account/two-factor with {"action":"setup"} | {"action":"enable","code":"123456"} | {"action":"disable","code":"123456"} - List account SSH keys: GET /account/ssh-keys (your own keys; each has id, name, fingerprint, publicKey, createdAt) - Register an SSH key: POST /account/ssh-keys with {"name":"laptop","publicKey":"ssh-ed25519 AAAA..."} (returns {id,name,fingerprint}; name must be unique; an invalid key → INVALID_PARAM) - Delete an SSH key: DELETE /account/ssh-keys/{id} (owner-only; not yours → NOT_FOUND) - Resend email verification: POST /account/verify-email/resend (your own account only; no body — returns {sent:true} or {sent:false,reason:"already_verified"}) - Affiliate status + stats: GET /account/affiliate (your own account only) — {active:false} until activated; then the full native affiliate page: {active:true, id, referralLink, visitors (clicks), signups, conversionRate, balanceCents (available), pendingCents (pending maturation), withdrawnCents, payoutMinimumCents, currency, referrals:[{date, service, amountCents, commissionCents, status}]}. Share referralLink: visits set a 90-day tracking cookie and count as clicks. (signups/conversionRate/pendingCents/referrals come from the affiliate read-bridge; a pre-deploy summary fallback returns visitors/balance/withdrawn + since only.) - Join the affiliate program: POST /account/affiliate/activate (no body; idempotent — already active just returns the current state) - Request an affiliate payout: POST /account/affiliate/withdraw (no body). When your balance is at/above the payout minimum it opens a support ticket for an admin to pay out in WHMCS (no funds move here). Returns {requested:true, ticketId, balanceCents, minimumCents, currency}, or {requested:false, reason:"not_affiliate"|"below_minimum", ...}. - Referral attribution: send people to https://console.rarecloud.io/register?aff=YOUR_ID (any console URL works — ?aff=N sets a 90-day first-party cookie, last touch wins). When they sign up, the account is linked to your affiliate id and recurring commissions accrue when their first service activates and pays. Self-referrals are rejected. - List support departments: GET /tickets/departments - Open a ticket (optionally with files): POST /tickets with {"subject":"...","department":"2","priority":"high","body":"...", "attachments":[{"name":"log.txt","data":""}]} - Responses use an {ok, data} envelope; errors use {ok:false, error:{code,message}}. ## AI code sandboxes (beta) Ephemeral, isolated execution environments for AI agents (like E2B), under `POST /api/v1/sandboxes`. Create one from a template (`python` with the data-science stack, `node`, or `base` for shell only), then: - `POST /api/v1/sandboxes/{id}/run_code` with `{language: "python"|"node", code}` returns `{stdout, stderr, exitCode}`. - `POST /api/v1/sandboxes/{id}/commands` with `{cmd}` runs a shell command. - `GET/PUT /api/v1/sandboxes/{id}/files` reads and writes files by absolute path. - `POST /api/v1/sandboxes/{id}/ports/{port}` returns a public preview URL. - `DELETE /api/v1/sandboxes/{id}` terminates it; sandboxes also auto-terminate after their idle `timeoutMs` (default 5 minutes, max 1 hour). Sandboxes are tenant-isolated: another account's sandbox id answers 404. Billing is per second of runtime. While the product is in beta the backend may answer in mock mode, in which case every response carries `mocked: true`; do not treat mocked output as a real execution. ## Quota limits the user will hit Every account has per-resource quota limits (vCPU, RAM, instances, volumes, disk gigabytes, floating IPs). `GET /api/v1/limits` returns them together with live usage and a per-resource state (`ok`, `warn` from 80%, `danger` from 95%, `unknown` when the region could not be read). Check it BEFORE proposing a deployment: a cluster or VM that does not fit is refused, and it is far better to tell the user up front than to have provisioning fail part-way through. A resource marked `unavailable` means we could not read it — that is not the same as "nothing used", so do not treat it as free headroom. For a managed-Kubernetes cluster, do the arithmetic with `POST /api/v1/limits/estimate` rather than by hand — send the worker pools (`machineType`, `minimum`, `maximum`) and it answers with `fits` (do the minimums fit, i.e. will the deploy start at all), `shortfall` (what is missing, keyed exactly as a limit-increase request should be), and per pool how many nodes are actually `reachable` out of the `configured` maximum plus the resource that is `limiting` it. A pool whose minimum fits but whose maximum does not is fine: it deploys and it autoscales, just not as far as configured. `available: false` means we could not work it out — that is unknown, not "does not fit". Limits are raised by asking: the console has a "Request limit increase" form (Cloud → Limits). Modest increases are approved automatically; larger ones open a support ticket for a human to review. ## Billing model the user will ask about Cloud resources are billed hourly and automatically capped at the plan's monthly price. The balance never goes into surprise debt. *Last updated: July 2026.*