Skip to content

API reference

This page covers the Spheron AI REST API for programmatic access to GPU instances, containers, SSH keys, volumes, and account balance.

Table of contents

Quick start

1. Get your API key

Generate an API key from your Spheron dashboard.

2. Make your first API call

Test your connection by listing available providers:

curl -H "Authorization: Bearer <your-api-key>" \
  https://app.spheron.ai/api/providers

3. List available GPUs

curl -H "Authorization: Bearer <your-api-key>" \
  "https://app.spheron.ai/api/gpu-offers?limit=5"

4. Deploy your first instance

curl -X POST "https://app.spheron.ai/api/deployments" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "spheron-ai",
    "offerId": "rtx-4090-spheron-ai-1",
    "gpuType": "rtx-4090",
    "gpuCount": 1,
    "region": "us-east-1",
    "operatingSystem": "ubuntu-22.04",
    "instanceType": "DEDICATED",
    "sshKeyId": "your_ssh_key_id"
  }'

Base URL

https://app.spheron.ai

Authentication

Include your API key in the Authorization header for all authenticated requests:

Authorization: Bearer <your-api-key>

Security note: Treat API keys like passwords. Never expose them in client-side code or public repositories. See Security best practices for more information.

Endpoints

Get providers

Get a list of all available GPU providers (public endpoint). Returns the subset of providers currently configured on the server.

Method: GET Path: /api/providers

Response example:
[
  "data-crunch",
  "massed-compute",
  "spheron-ai",
  "spheron-es",
  "spheron-ms",
  "spheron-am",
  "spheron-con"
]

Get GPU offers

Get available offers with filtering and pagination. Authenticate to see team discounts.

By default this endpoint returns virtual machines: GPU offers and the single CPU Node row. Spheron Con containers are listed only when you ask for them: computeType=all lists virtual machines and containers together, and computeType=container lists containers only. See CPU offers for the CPU-specific shape.

Spheron AI offers in the noida-india and chennai-india regions, CPU nodes included, are on-demand only (instanceType: "DEDICATED") and report supportsCloudInit: false.

A Spheron ES spot offer whose price is live carries extras.live_spot_price: true. For such an offer, spot_price and price hold the current spot price, which can differ between calls. A Spheron ES spot offer is left out of the response while no current spot price is available for it.

Method: GET Path: /api/gpu-offers

Headers:
  • Authorization: Bearer <your-api-key> (optional)
Parameters:
NameTypeRequiredDescription
pagenumberNoPage number (default: 1)
limitnumberNoItems per page (default: 10)
searchstringNoSearch term for GPU models
sortBystringNoSort field (default: popularity)
sortOrderstringNoasc or desc (default: asc)
instanceTypestringNoFilter by type: 'SPOT', 'DEDICATED', or 'CLUSTER' (case-insensitive)
computeTypestringNoFilter by product: vm returns every virtual machine (GPU, CPU and bare metal), gpu returns GPU virtual machines and bare metal, cpu returns only the CPU Node row, gpu-container and cpu-container return one kind of container, container returns both, and all returns virtual machines and containers together. Omit to list virtual machines only (default). An unknown value lists virtual machines only too.
Response example:
{
  "data": [
    {
      "gpuType": "rtx-4090",
      "gpuModel": "RTX 4090",
      "baseGpuType": "rtx-4090",
      "interconnectVariants": ["PCIe"],
      "hasMultipleVariants": false,
      "displayName": "RTX 4090",
      "popularity": 85,
      "totalAvailable": 12,
      "lowestPrice": 0.45,
      "highestPrice": 0.65,
      "averagePrice": 0.55,
      "providers": ["massed-compute", "spheron-ai"],
      "offers": [
        {
          "provider": "massed-compute",
          "offerId": "rtx-4090-massed-compute-1-dedicated",
          "name": "RTX 4090",
          "description": "High-performance gaming and AI GPU",
          "vcpus": 8,
          "memory": 32,
          "storage": 100,
          "gpuCount": 1,
          "price": 0.50,
          "available": true,
          "clusters": ["us-east-1", "us-west-2"],
          "gpu_memory": 24,
          "os_options": ["ubuntu-20.04", "ubuntu-22.04"],
          "maintenance": false,
          "interconnectType": "PCIe",
          "interconnectDescription": "PCIe interface",
          "instanceType": "DEDICATED",
          "supportsCloudInit": true,
          "extras": {
            "deployment_type": "vm",
            "networking_type": "standard"
          }
        },
        {
          "provider": "data-crunch",
          "offerId": "rtx-4090-data-crunch-1-spot",
          "name": "RTX 4090 SPOT",
          "description": "Cost-effective SPOT instance (lower price, may be interrupted)",
          "vcpus": 8,
          "memory": 32,
          "storage": 100,
          "gpuCount": 1,
          "price": 0.25,
          "spot_price": 0.25,
          "available": true,
          "clusters": ["us-east-1", "eu-west-1"],
          "gpu_memory": 24,
          "os_options": ["ubuntu-20.04", "ubuntu-22.04"],
          "maintenance": false,
          "interconnectType": "PCIe",
          "interconnectDescription": "PCIe interface",
          "instanceType": "SPOT",
          "supportsCloudInit": true,
          "extras": {
            "deployment_type": "vm",
            "networking_type": "standard"
          },
          "note": "For SPOT instances: price = spot_price (discounted rate with possible interruption)"
        },
        {
          "provider": "spheron-ai",
          "offerId": "baremetal_h100_sxm5_8",
          "name": "H100 SXM5 8x GPU Bare Metal",
          "description": "8x H100 SXM5 GPUs, 832 vCPUs, 8192GB RAM, 144000GB Storage. Bare Metal with NVLink between GPUs.",
          "vcpus": 832,
          "memory": 8192,
          "storage": 144000,
          "gpuCount": 8,
          "price": 15.84,
          "available": true,
          "clusters": ["dalas-united states"],
          "region": "dalas-united states",
          "gpu_memory": 80,
          "os_options": ["Ubuntu 20.04 LTS", "Ubuntu 22.04 LTS"],
          "maintenance": false,
          "interconnectType": "NVLink",
          "interconnectDescription": "NVLink high-speed GPU interconnect",
          "instanceType": "CLUSTER",
          "supportsCloudInit": true,
          "extras": {
            "deployment_type": "cluster",
            "networking_type": "ethernet"
          }
        }
      ]
    }
  ],
  "total": 50,
  "page": 1,
  "limit": 10,
  "totalPages": 5
}

CPU offers

CPU Node is a CPU-only instance with no GPU attached. It is served by the same /api/gpu-offers endpoint as a single row, not as one entry per provider.

Filter to CPU only:
curl -H "Authorization: Bearer <your-api-key>" \
  "https://app.spheron.ai/api/gpu-offers?computeType=cpu"

?search=cpu returns the same row. Passing computeType=gpu excludes it, and omitting the parameter lists it with the other virtual machines.

What the row looks like:
  • gpuType is "CPU" and displayName is "CPU NODE".
  • Every entry in offers[] reports gpuCount: 0 and gpu_memory: 0.
  • Size is described by vcpus and memory rather than a GPU model.
  • clusters holds the region, os_options holds the deployable images, and offerId is what a deployment takes.
Response example (one offer from the CPU Node row):
{
  "data": [
    {
      "gpuType": "CPU",
      "gpuModel": "",
      "baseGpuType": "CPU",
      "displayName": "CPU NODE",
      "totalAvailable": 29,
      "lowestPrice": 0.0862,
      "providers": ["data-crunch", "spheron-ai"],
      "offers": [
        {
          "provider": "data-crunch",
          "offerId": "CPU.4V.16G_FIN-01_200_spot",
          "name": "0x AMD EPYC",
          "description": "0x AMD EPYC, 4 vCPUs, 16GB RAM.",
          "vcpus": 4,
          "memory": 16,
          "storage": 200,
          "gpuCount": 0,
          "gpu_memory": 0,
          "price": 0.0862,
          "spot_price": 0.0862,
          "available": true,
          "clusters": ["Finland 1"],
          "os_options": ["Jupyter", "Ubuntu 22.04", "Ubuntu 24.04"],
          "maintenance": false,
          "instanceType": "SPOT",
          "supportsCloudInit": true,
          "extras": {
            "deployment_type": "vm"
          }
        }
      ]
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 10,
  "totalPages": 1
}
Deploy a CPU offer:

Send gpuType: "CPU" with gpuCount: 0 to POST /api/deployments. Every other parameter works the way it does for a GPU offer.

curl -X POST "https://app.spheron.ai/api/deployments" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "data-crunch",
    "offerId": "CPU.4V.16G_FIN-01_200_spot",
    "gpuType": "CPU",
    "gpuCount": 0,
    "region": "Finland 1",
    "operatingSystem": "Ubuntu 22.04",
    "instanceType": "SPOT",
    "sshKeyId": "your_ssh_key_id"
  }'

Deployments created this way are stored as CPU nodes, so responses and usage records report a CPU node rather than an unnamed GPU.

See CPU Nodes for sizes, regions, and the dashboard flow.

Containers (Spheron Con)

Spheron Con runs a public Docker image on a GPU or a CPU instead of a full virtual machine. A container is usually ready within a few minutes, longer for a large image, and is billed by the hour. Its offers come from the same /api/gpu-offers endpoint and it deploys through the same POST /api/deployments, with a container object in place of an operating system.

List container offers:
curl "https://app.spheron.ai/api/gpu-offers?computeType=gpu-container&search=a40"

computeType=cpu-container lists the CPU container row and computeType=container lists both kinds.

What a container offer looks like:
  • provider is spheron-con, and the group's gpuType ends in _CONTAINER (A40_PCIE_CONTAINER, or CPU_CONTAINER for CPU containers). A GPU sold both ways has two groups, such as A40_PCIE and A40_PCIE_CONTAINER.
  • os_options is ["Container"], supportsCloudInit is false, instanceType is DEDICATED and minimumRuntimeMinutes is 20, the same minimum as a virtual machine.
  • extras.container describes what comes with it: compute (gpu or cpu), containerDiskGb (100 on a GPU container), and on a GPU container volumeGb and volumeMountPath (a 500 GB volume at /workspace), stoppedHourlyUsd (what the container bills per hour while stopped) and volumeHourlyUsd (what the built-in volume adds to the running price, dropped when you pass your own volume in volumeIds). specsAreMinimums: true means vcpus and memory are what you get at least.
  • extras.volumes_supported says whether a Spheron Con volume can be used in that offer's region. extras.lifecycle.canPause is true for GPU containers and false for CPU containers.
{
  "provider": "spheron-con",
  "offerId": "rpg__nvidia-a40__1__CA-MTL-1",
  "name": "1x A40_PCIE",
  "vcpus": 9,
  "memory": 50,
  "storage": 600,
  "gpuCount": 1,
  "price": 0.76929,
  "available": true,
  "clusters": ["CA-MTL-1"],
  "gpu_memory": 48,
  "os_options": ["Container"],
  "instanceType": "DEDICATED",
  "supportsCloudInit": false,
  "minimumRuntimeMinutes": 20,
  "extras": {
    "deployment_type": "container",
    "volumes_supported": false,
    "container": { "compute": "gpu", "containerDiskGb": 100, "volumeGb": 500, "volumeMountPath": "/workspace", "stoppedHourlyUsd": 0.164383, "volumeHourlyUsd": 0.082192, "specsAreMinimums": true },
    "lifecycle": { "canPause": true, "canRestart": true }
  }
}
Check the image first:
curl -H "Authorization: Bearer <your-api-key>" \
  "https://app.spheron.ai/api/containers/image?image=pytorch/pytorch:latest"
{ "status": "found", "ssh": true, "user": "root" }

status is found, not_found (which includes private images) or unknown when the registry could not be reached. ssh is true when SSH can be set up on the image (it runs as root), false when it cannot (the image runs as another user, named in user, so use its ports instead), and null when it could not be told.

Deploy a container:
curl -X POST "https://app.spheron.ai/api/deployments" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "spheron-con",
    "offerId": "rpg__nvidia-a40__1__CA-MTL-1",
    "gpuType": "A40_PCIE_CONTAINER",
    "gpuCount": 1,
    "region": "CA-MTL-1",
    "instanceType": "DEDICATED",
    "sshKeyId": "your_ssh_key_id",
    "container": {
      "image": "pytorch/pytorch:latest",
      "ports": [8888],
      "env": { "HF_TOKEN": "hf_..." }
    }
  }'

For a CPU container, send gpuType: "CPU_CONTAINER" and gpuCount: 0.

container fieldDescription
imageRequired. A public image from Docker Hub, GHCR, Quay or another registry. A private or nonexistent image returns 400 before anything is created or charged.
commandReplaces the image's own start command, as one shell-style line.
portsUp to 9 ports. Each gets its own public port. Port 22 is added for SSH when you send an SSH key.
envUp to 50 variables. Names use letters, digits and underscores; PUBLIC_KEY and SPHERON_SSH_KEYS are reserved. Values reach the container at start and are not stored: responses return only the names, in container.envKeys.
  • operatingSystem is not needed and is set to the image. cloudInit is refused.
  • An SSH key is optional for a container. Without one, the container runs with no SSH, port 22 stays closed and no sshCommand is returned. Images that run as a non-root user, such as Jupyter, have no SSH either way and are used through their ports.
  • volumeIds takes at most one Spheron Con volume, in the same region. It is mounted at /workspace in place of the built-in volume, whose cost (extras.container.volumeHourlyUsd, about $0.08 per hour) is taken off the container's hourly rate, and it cannot be added after launch. Several containers can share one volume.
While it starts:

A container is billed from the moment its machine starts, including while its image downloads, because the machine is reserved for you from then. Its status is running from that moment, with containerState saying it is not reachable yet:

{
  "status": "running",
  "containerState": { "phase": "downloading", "since": "2026-10-30T15:48:31Z", "issue": null }
}

phase is downloading while the image downloads and starting once it starts. When containerState is gone, ipAddress, sshCommand and portForwards are usable: open http://<ipAddress>:<external_port> for each port. Public ports can change after a restart or Start, so read them again afterwards.

  • A container whose start command or image exits right away never comes up. It is failed within about two minutes, with a failureReason that says so, and billed only for the minutes it ran.
  • A container that ran and then keeps exiting stays running with containerState.issue: "crash_loop". Check its image and command, then restart or terminate it.
  • A container that has not started after 90 minutes is shut down.

Sign-in secrets: a container started with JUPYTER_TOKEN (Jupyter) or WEBUI_ADMIN_PASSWORD (Open WebUI) can read it back. Spheron does not store it; it is read from the running container.

curl -H "Authorization: Bearer <your-api-key>" \
  "https://app.spheron.ai/api/deployments/<deploymentId>/access-token"
{ "token": "bf6077abc906545d860cecd204b77e51" }

It returns 404 when the container was started without one, 409 until it is running, and 502 if it cannot be read right now.

Stop, Start and Restart: only GPU containers can be stopped; pause on a CPU container returns 400. A stop keeps only /workspace and an attached volume, and a restart resets everything else on the container disk too. A stopped GPU container starts again only on the machine that holds it, so resume returns 503 while that machine's GPU is taken. The container stays stopped with its files, and you can try again later, or terminate it and deploy a new one if you cannot wait.

Deployments

Create deployment

Create a GPU or CPU instance, or a container.

Method: POST Path: /api/deployments

Headers:
  • Authorization: Bearer <your-api-key>
  • Content-Type: application/json
Parameters:
NameTypeRequiredDescription
providerstringYesProvider name (e.g., data-crunch, massed-compute, spheron-ai, spheron-es, spheron-ms, spheron-am, spheron-con). Call GET /api/providers for the live list of configured providers.
offerIdstringYesUnique offer ID from the GPU offers endpoint
gpuTypestringYesGPU type identifier (e.g., rtx-4090, h100). Send CPU for a CPU Node offer.
gpuCountnumberYesNumber of GPUs to deploy. Send 0 for a CPU Node offer.
regionstringYesDeployment region or cluster. Send the offer's clusters value back unchanged.
operatingSystemstringYesOperating system (e.g., ubuntu-20.04, ubuntu-22.04). Not needed for a container, where it is set to container.image.
containerobjectConditionalRequired for a container offer and refused for a virtual machine: image, and optionally command, ports and env.
instanceTypestringYesInstance type: 'SPOT', 'DEDICATED', or 'CLUSTER' (case-insensitive)
sshKeyIdstringConditionalSSH key ID from your saved keys. Send this or ssh_public_key (optional for a container, which then runs with no SSH). A request with neither, or whose saved key has missing or invalid public key data, returns 400 before any deployment is created.
ssh_public_keystringConditionalInline SSH public key content. Send this or sshKeyId. A temporary key is created for this deployment.
teamIdstringNoTeam ID for team deployments (optional)
namestringNoOptional name for the deployment
cloudInitobjectNoCloud-Init configuration object. Supports fields: runcmd (string[]), packages (string[]), writeFiles (array of { path, content, owner?, permissions? }). Check the offer's supportsCloudInit flag first. Sending cloudInit for an offer that reports false returns 400. On Spheron ES, the commands, packages, and files, together with the SSH keys added to the machine, must fit in 32,768 characters. A larger configuration returns 400 naming the limit.
volumeIdsstring[]NoIDs of persistent volumes to attach at launch. Pass either the volume's id or its volumeId; both are accepted. A volume that does not exist returns 400. Each volume must match the deployment's provider and region. Per-provider rules: Spheron AI: depends on the region. In Canada 1, Montreal 1, Norway 1, Oslo 1, and the US, up to 10 volumes per instance, each volume can only be attached to one instance at a time (hot-detach and re-attach supported post-launch). In Noida and Chennai, a single-item array of a shared volume in the same region, which several deployments can use; Verda: up to 10 volumes per instance, each volume can be shared across multiple instances; Spheron ES: multiple volumes per instance, each volume can be shared across multiple instances read-write or read-only, and attaching at deploy time avoids the VM restart that a hot-attach causes; Spheron MS: single-item array, and the volume's region must equal the offer's region or the request is rejected before anything is created; Spheron AM: volumes are not available, and a request carrying volumeIds is rejected rather than created without them; Spheron Con: single-item array of a Spheron Con volume in the same region, mounted at /workspace, and it cannot be added after launch. A request that sends volumeIds for a machine that cannot take a volume, such as a Spheron AI CPU node in Noida or Chennai, returns 400.
maxHourlyRatenumberNoMax price in USD per hour for the whole instance, from 0.01 to 1000, and at least the price the instance starts at. Applies only to offers with extras.live_spot_price: true (Spheron ES spot) and is ignored for other offers. When the live spot price rises above it, the instance is stopped. See Set max price. An invalid value returns 400 before anything is created.
Request body example:
{
  "provider": "massed-compute",
  "offerId": "rtx-4090-spheron-ai-1",
  "gpuType": "rtx-4090",
  "gpuCount": 1,
  "region": "us-east-1",
  "operatingSystem": "ubuntu-20.04",
  "instanceType": "DEDICATED",
  "sshKeyId": "ssh_key_id_here",
  "teamId": "team_id_optional",
  "name": "My GPU Instance",
  "note": "Alternatively, use 'ssh_public_key' instead of 'sshKeyId' to create a temporary SSH key",
  "cloudInit": {
    "runcmd": [
      "echo 'Hello from cloud-init'",
      "apt-get update"
    ],
    "packages": ["curl", "git"],
    "writeFiles": [
      {
        "path": "/etc/motd",
        "content": "Welcome to your instance!",
        "owner": "root:root",
        "permissions": "0644"
      }
    ]
  },
  "volumeIds": ["vol_abc123"]
}
Response example:
{
  "id": "deployment_id",
  "name": "My GPU Instance",
  "userId": "user_id",
  "teamId": "team_id",
  "gpuModelId": "rtx-4090-rtx-4090-spheron-ai-1",
  "gpuType": "rtx-4090",
  "offerId": "rtx-4090-spheron-ai-1",
  "providerId": "spheron-ai",
  "gpuCount": 1,
  "region": "us-east-1",
  "operatingSystem": "ubuntu-20.04",
  "instanceType": "DEDICATED",
  "sshKeyId": "ssh_key_id_here",
  "tempSshKeyId": null,
  "sshKeyName": "My SSH Key",
  "sshKeyFingerprint": "SHA256:abc123...",
  "ipAddress": null,
  "user": null,
  "status": "deploying",
  "startedAt": null,
  "stoppedAt": null,
  "lastCreditDeduction": null,
  "totalCost": 0,
  "hourlyRate": 0.50,
  "originalHourlyRate": 0.50,
  "discountPercentage": 0,
  "hasDiscount": false,
  "vcpus": 8,
  "memory": 32,
  "storage": 100,
  "sshCommand": null,
  "sshPort": null,
  "portForwards": [],
  "createdAt": "2024-01-15T10:30:00Z"
}
Errors:
StatusCodeMeaningWhat to do
400validation_errorA field is missing or invalid: no usable SSH key, an unknown provider, a missing operatingSystem, a gpuType that does not match the offer, a volume that does not exist, an invalid maxHourlyRate, a startup configuration over the provider's size limit, or for a container a missing or invalid container, a private or nonexistent image, or cloudInitRead error, which names the field (and details.field when set). Fix the request; retrying it unchanged fails again.
400agent_access_unsupported, agent_access_unavailableThe Agent GPU Sandbox cannot be added to this offer, or is unavailable right nowDeploy without the sandbox, or pick an offer from another provider.
402insufficient_creditsYour team balance cannot cover the minimum runtime of this offer. details.requiredAmount and details.balance give the amounts in dollarsAdd credits, then retry.
409offer_unavailableThe offer sold out between listing and deploying, or its cloud is temporarily unavailableRefresh GET /api/gpu-offers and pick another offer or region.
409deployment_in_progressAnother deployment for your team is being created right nowWait for it to finish, then retry.
422spend_cap_exceededThrough an AI connection: the deployment would go over the connection's spend capRaise the cap under Settings, AI connections, or deploy from the dashboard.

A deployment refused with one of these codes is never created and never billed.

Get deployments

Get a list of your deployments with optional filtering.

Method: GET Path: /api/deployments

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
userIdstringNoFilter by user ID (must be your own)
teamIdstringNoFilter by team ID
statusstringNoFilter by status: active (running/deploying/stopped), inactive (terminated/failed), or a specific status value: running, deploying, stopped, terminated, failed
Response example:
[
  {
    "id": "deployment_id",
    "name": "My GPU Instance",
    "userId": "user_id",
    "teamId": "team_id",
    "gpuModelId": "rtx-4090-spheron-ai-1",
    "gpuType": "rtx-4090",
    "offerId": "rtx-4090-spheron-ai-1",
    "providerId": "spheron-ai",
    "gpuCount": 1,
    "region": "us-east-1",
    "operatingSystem": "ubuntu-20.04",
    "instanceType": "DEDICATED",
    "sshKeyId": "ssh_key_id_here",
    "tempSshKeyId": null,
    "sshKeyName": "My SSH Key",
    "sshKeyFingerprint": "SHA256:abc123...",
    "ipAddress": "192.168.1.100",
    "user": "ubuntu",
    "status": "running",
    "startedAt": "2024-01-15T10:30:00Z",
    "stoppedAt": null,
    "lastCreditDeduction": "2024-01-15T11:30:00Z",
    "totalCost": 12.50,
    "hourlyRate": 0.50,
    "stoppedHourlyRate": null,
    "pausedAt": null,
    "totalPausedHours": 0,
    "supportsPause": true,
    "supportsRestart": true,
    "pauseUnsupportedReason": null,
    "providerMinimumRuntimeMinutes": null,
    "hasDiscount": false,
    "discountPercentage": 0,
    "originalHourlyRate": 0.50,
    "vcpus": 8,
    "memory": 32,
    "storage": 100,
    "sshCommand": "ssh ubuntu@192.168.1.100",
    "sshPort": 22,
    "portForwards": [],
    "createdAt": "2024-01-15T10:30:00Z"
  }
]

Get deployment details

Get details of a specific deployment by ID.

Method: GET Path: /api/deployments/{deploymentId}

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
deploymentIdstringYesDeployment ID
Response example:
{
  "id": "deployment_id",
  "name": "My GPU Instance",
  "userId": "user_id",
  "teamId": "team_id",
  "gpuModelId": "rtx-4090-spheron-ai-1",
  "gpuType": "rtx-4090",
  "offerId": "rtx-4090-spheron-ai-1",
  "providerId": "spheron-ai",
  "gpuCount": 1,
  "region": "us-east-1",
  "operatingSystem": "ubuntu-20.04",
  "instanceType": "DEDICATED",
  "sshKeyId": "ssh_key_id_here",
  "tempSshKeyId": null,
  "sshKeyName": "My SSH Key",
  "sshKeyFingerprint": "SHA256:abc123...",
  "ipAddress": "192.168.1.100",
  "user": "ubuntu",
  "status": "running",
  "startedAt": "2024-01-15T10:30:00Z",
  "stoppedAt": null,
  "lastCreditDeduction": "2024-01-15T11:30:00Z",
  "totalCost": 12.50,
  "hourlyRate": 0.50,
  "hasDiscount": false,
  "discountPercentage": 0,
  "originalHourlyRate": 0.50,
  "vcpus": 8,
  "memory": 32,
  "storage": 100,
  "sshCommand": "ssh ubuntu@192.168.1.100",
  "sshPort": 22,
  "portForwards": [],
  "extras": {},
  "createdAt": "2024-01-15T10:30:00Z"
}

A Spheron ES spot deployment on the live spot price returns liveSpotPrice: true. Its hourlyRate can change while it runs, up to the dedicated price for the same machine.

Live-priced spot deployments also carry these fields:

  • maxHourlyRate: The max price in USD per hour for the whole instance, or null for no limit. See Set max price.
  • pauseInitiator: "price-cap" when the instance was stopped because the spot price rose above maxHourlyRate. The status is stopped.
  • stoppedHourlyRate: What a stopped instance is billed. While it is stopped, hourlyRate keeps following the live spot price, so compare it with maxHourlyRate to know when a start is allowed.

Rename deployment

Update the display name of an existing deployment. Only the name field is mutable. Provider, region, GPU type, instance type, SSH key, attached volumes, and every other deployment attribute are immutable after creation. To change anything else, terminate this deployment and create a new one. The response is the full updated deployment in the same shape returned by GET /api/deployments/{deploymentId}.

Method: PATCH Path: /api/deployments/{deploymentId}

Headers:
  • Authorization: Bearer <your-api-key>
  • Content-Type: application/json
Parameters:
NameTypeRequiredDescription
deploymentIdstringYesDeployment ID to update
Request body example:
{
  "name": "Renamed Instance"
}
Response example:
{
  "id": "deployment_id",
  "name": "Renamed Instance",
  "userId": "user_id",
  "teamId": "team_id",
  "gpuModelId": "rtx-4090-rtx-4090-massed-compute-1",
  "gpuType": "rtx-4090",
  "offerId": "rtx-4090-massed-compute-1",
  "providerId": "massed-compute",
  "gpuCount": 1,
  "region": "us-east-1",
  "operatingSystem": "ubuntu-20.04",
  "instanceType": "DEDICATED",
  "sshKeyId": "ssh_key_id_here",
  "tempSshKeyId": null,
  "sshKeyName": "My SSH Key",
  "sshKeyFingerprint": "SHA256:abc123...",
  "ipAddress": "192.168.1.100",
  "user": "ubuntu",
  "status": "running",
  "startedAt": "2024-01-15T10:30:00Z",
  "stoppedAt": null,
  "lastCreditDeduction": "2024-01-15T11:30:00Z",
  "totalCost": 12.50,
  "hourlyRate": 0.50,
  "hasDiscount": false,
  "discountPercentage": 0,
  "originalHourlyRate": 0.50,
  "vcpus": 8,
  "memory": 32,
  "storage": 100,
  "sshCommand": "ssh ubuntu@192.168.1.100",
  "sshPort": 22,
  "portForwards": [],
  "createdAt": "2024-01-15T10:30:00Z"
}

Terminate deployment

Terminate a deployment and its associated cloud instance.

Method: DELETE Path: /api/deployments/{deploymentId}

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
deploymentIdstringYesDeployment ID to terminate
Response example:
{
  "message": "Instance destruction initiated",
  "deployment": {
    "id": "deployment_id",
    "status": "terminated",
    "stoppedAt": "2024-01-15T12:30:00Z"
  }
}

Check if deployment can be terminated

Check whether a deployment has met the minimum runtime and can be safely terminated. Use this before calling the terminate endpoint to avoid rejected requests.

Method: GET Path: /api/deployments/{deploymentId}/can-terminate

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
deploymentIdstringYesDeployment ID to check
Response example:
{
  "canTerminate": true,
  "runtime": 25,
  "timeRemaining": 0,
  "minimumRuntime": 20
}

Instance lifecycle

Stop an instance to pause GPU billing while keeping its disk, start it again, or reboot it in place. Support varies by provider, machine type, and region. See Instance lifecycle for the support matrix and the billing rules.

Every endpoint below acts on a deployment you own or that belongs to your team. Asking a provider for an action it cannot perform returns an error that names the limit rather than a generic failure.

Quote a stop

Return the current rate, the stopped rate, and the breakdown behind it. This endpoint changes nothing, so call it before pause to show the user the same number the stop will apply.

Method: GET Path: /api/deployments/{deploymentId}/pause-quote

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
deploymentIdstringYesDeployment to quote
Response example:
{
  "hourlyRate": 0.0412,
  "originalHourlyRate": 0.0412,
  "currentHourlyRate": 2.45,
  "breakdown": [
    { "label": "Root disk (512 GB)", "amount": 0.0362 },
    { "label": "Public IP (retained)", "amount": 0.005 }
  ]
}

Stop deployment

Stop the instance. The GPU is released, the disk and its contents are preserved, and billing drops to the stopped rate. Only a running instance can be stopped. On a GPU container, only /workspace and an attached volume are kept; CPU containers cannot be stopped (see Containers).

Method: POST Path: /api/deployments/{deploymentId}/pause

Headers:
  • Authorization: Bearer <your-api-key>
Example request:
curl -X POST "https://app.spheron.ai/api/deployments/<deployment-id>/pause" \
  -H "Authorization: Bearer <your-api-key>"

Response: The updated deployment object, with status set to stopped and stoppedHourlyRate populated.


Start deployment

Start a stopped instance. It comes back on the same disk with the same contents and the same public IP. Only a stopped instance can be started. A GPU container comes back on the machine that holds it, with new public ports, and is refused with 503 while that machine's GPU is taken.

Method: POST Path: /api/deployments/{deploymentId}/resume

Headers:
  • Authorization: Bearer <your-api-key>
Example request:
curl -X POST "https://app.spheron.ai/api/deployments/<deployment-id>/resume" \
  -H "Authorization: Bearer <your-api-key>"

Response: The updated deployment object.

A Spheron ES spot instance with a max price returns 409 while its hourlyRate is above maxHourlyRate. Wait for the price to fall, or raise or clear the max price with Set max price, then start it.

On Spheron AI in Noida and Chennai, a started instance can come back under a new instanceId. Read the deployment again with Get deployment details after a start.

A start can be refused when the provider released the hardware while the instance was stopped and the machine type is currently out of stock. The refusal is not destructive: the instance and its disk are unchanged, so retry when stock returns.


Set max price

Set or clear the max price of a Spheron ES spot instance on the live spot price. When the live spot price rises above the max, the instance is stopped exactly as if you had called Stop deployment: the disk, data, and IP address are kept, and it bills at stoppedHourlyRate. A price equal to the max does not stop it. See Set a max price for the dashboard flow.

Method: PUT Path: /api/deployments/{deploymentId}/max-price

Headers:
  • Authorization: Bearer <your-api-key>
  • Content-Type: application/json
Parameters:
NameTypeRequiredDescription
deploymentIdstringYesDeployment to update (path)
maxHourlyRatenumber or nullYesMax price in USD per hour for the whole instance, from 0.01 to 1000. On a running deployment, it must be at least the current hourlyRate. Send null to remove the limit.
Example request:
curl -X PUT "https://app.spheron.ai/api/deployments/<deployment-id>/max-price" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{ "maxHourlyRate": 12.5 }'

Response: The updated deployment object, with maxHourlyRate set.

The call is allowed on running and stopped deployments with liveSpotPrice: true. Any other deployment, or a value outside the rules above, returns 400 with a message saying why.


Restart deployment

Reboot the instance in place. Billing does not change and the instance keeps its status. This is a hard reboot, so anything held in memory is lost. Only a running instance can be restarted. A container also gets a fresh container disk: only /workspace and an attached volume keep their files.

Method: POST Path: /api/deployments/{deploymentId}/restart

Headers:
  • Authorization: Bearer <your-api-key>
Example request:
curl -X POST "https://app.spheron.ai/api/deployments/<deployment-id>/restart" \
  -H "Authorization: Bearer <your-api-key>"

Response: The updated deployment object.


Get restart progress

Report whether a reboot is still in flight, asked of the provider rather than read from a cached record. Poll this while a restart runs.

Method: GET Path: /api/deployments/{deploymentId}/reboot-status

Headers:
  • Authorization: Bearer <your-api-key>
Response example:
{
  "restarting": true,
  "since": "2026-09-08T10:31:04Z"
}

restarting is false and since is absent when no reboot is in flight.


Lifecycle errors

StatusCodeMeaningWhat to do
400action_not_allowed, action_not_supportedThe instance's type or status does not allow this action, or the provider cannot perform itRead error for the specific limit. Do not retry.
400instance_not_readyThe instance has no machine at the provider yetWait until it is running, then retry.
402insufficient_creditsA start needs at least 30 minutes of the instance's running rate in balance. details.requiredAmount and details.balance give the amountsAdd credits, then start it.
403deployment_access_deniedThe deployment belongs to another user or teamCheck the deployment ID.
404deployment_not_foundNo deployment with that IDCheck the deployment ID.
409instance_busy, instance_already_startingAnother change is still in flight on this instance, such as a volume change or an earlier restart, or it is already startingWait a few minutes and retry.
409above_max_priceOn a start: a spot instance's hourlyRate is above its maxHourlyRateWait for the price to fall, or change the max price.
422spend_cap_exceededThrough an AI connection: the start would go over the connection's spend capRaise the cap, or start it from the dashboard.
502provider_action_failed, pricing_unavailableThe provider refused the action, or its stopped rate could not be determinedRead error. Nothing was changed.
503provider_start_failedA start was refused, usually because the machine type is out of stock, or for a GPU container because the GPU on its machine is takenRetry later. The instance and its data are intact.
504provider_timeoutThe provider is taking longer than usualThe request went through. Poll the deployment instead of retrying.

Check supportsPause, supportsRestart, and pauseUnsupportedReason on the deployment object before offering a control, so a user is never shown a button that always fails.

SSH keys

Get SSH keys

Get a list of your SSH keys.

Method: GET Path: /api/ssh-keys

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
userIdstringNoFilter by user ID (must be your own)
teamIdstringNoFilter by team ID
Response example:
[
  {
    "id": "ssh_key_id",
    "userId": "user_id",
    "teamId": "team_id",
    "name": "My SSH Key",
    "publicKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB...",
    "fingerprint": "sha256:abc123def456...",
    "createdAt": "2024-01-15T10:30:00Z"
  }
]

Add SSH key

Add a new SSH key.

Method: POST Path: /api/ssh-keys

Headers:
  • Authorization: Bearer <your-api-key>
  • Content-Type: application/json
Request body:
{
  "name": "My SSH Key",
  "publicKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... user@hostname",
  "teamId": "team_id_optional"
}
Response example:
{
  "id": "ssh_key_id",
  "userId": "user_id",
  "teamId": "team_id",
  "name": "My SSH Key",
  "publicKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB...",
  "fingerprint": "sha256:abc123def456...",
  "createdAt": "2024-01-15T10:30:00Z"
}

Get SSH key details

Get a specific SSH key by ID.

Method: GET Path: /api/ssh-keys/{id}

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
idstringYesSSH key ID
Response example:
{
  "id": "ssh_key_id",
  "userId": "user_id",
  "teamId": "team_id",
  "name": "My SSH Key",
  "publicKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB...",
  "fingerprint": "sha256:abc123def456...",
  "createdAt": "2024-01-15T10:30:00Z"
}

Delete SSH key

Delete an SSH key.

Method: DELETE Path: /api/ssh-keys/{id}

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
idstringYesSSH key ID to delete
Response example:
{
  "success": true
}

Volumes

Supported providers: data-crunch, spheron-ai, spheron-es, spheron-ms, and spheron-con. Each provider has different rules around size limits, regions, attachment, and mutability. Before creating a volume, call GET /api/volumes/regions?provider=<id> to discover valid regions.

Volume IDs: Spheron assigns every new volume its own volumeId, in the form vol_ followed by 24 hexadecimal characters. A volume created before this format still resolves by its old ID on every /api/volumes/{volumeId} call and in volumeIds on POST /api/deployments, so stored IDs keep working.

Spheron AI volume types: the region decides the kind of volume. Spheron AI volumes are block volumes in Canada 1, Montreal 1, Norway 1, Oslo 1, and the US, and shared filesystems from 50 GB to 2,048 GB in noida-india and chennai-india. Select a shared volume at deploy time with volumeIds. It cannot be attached to a running deployment, resized, or renamed, and it mounts automatically at /home/jl_fs.

List volumes

List all persistent storage volumes for a team with pagination and status filtering.

Method: GET Path: /api/volumes

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
teamIdstringNoFilter by team ID (defaults to current team)
pagenumberNoPage number (default: 1)
limitnumberNoItems per page (default: 20)
statusstringNoFilter by status: 'available', 'attached', 'deleting', or 'deleted'
Response example:
{
  "volumes": [{
    "id": "volume_internal_id",
    "volumeId": "vol_abc123",
    "name": "my-data-volume",
    "userId": "user_id",
    "teamId": "team_id",
    "providerId": "data-crunch",
    "sizeInGb": 100,
    "usedCapacityBytes": 52428800000,
    "virtualIp": "192.168.100.50",
    "attachedToDeploymentIds": ["deployment_id_1"],
    "attachedToOrderIds": ["order_123"],
    "status": "attached",
    "hourlyRate": 0.0137,
    "lastPriceUpdate": "2026-02-07T10:30:00Z",
    "createdAt": "2026-02-07T10:00:00Z",
    "lastAttachedAt": "2026-02-07T10:15:00Z"
  }],
  "total": 5,
  "page": 1,
  "limit": 20,
  "totalPages": 1
}

Create volume

Create a new persistent storage volume. Supported providers: data-crunch, spheron-ai, spheron-es, spheron-ms, spheron-con. Spheron Con volumes are for containers: pick one in volumeIds when you deploy a container in the same region. They cannot be passed a deploymentId here. Optionally attach to an existing deployment during creation for data-crunch, spheron-ai, and spheron-es. Attaching a Spheron ES volume to a running instance restarts the VM. Spheron MS volumes bind at instance launch only, so they cannot be passed a deploymentId here and require a region.

Method: POST Path: /api/volumes

Headers:
  • Authorization: Bearer <your-api-key>
  • Content-Type: application/json
Parameters:
NameTypeRequiredDescription
namestringYesVolume name (lowercase alphanumeric with hyphens/underscores, max 60 chars)
sizeInGbnumberYesVolume size in whole GB. A fractional value such as 100.5 returns 400; a numeric string such as "200" is accepted. Accepted up to the maxSizeGb that GET /api/volumes/regions?provider=<id> returns for your region. Per-provider limits: data-crunch 1-10000, spheron-con 10-4096, spheron-ms 1-16000, spheron-es up to 262144 (256 TiB), spheron-ai up to 51200 (the cap can vary by region. Call GET /api/volumes/regions?provider=spheron-ai to read minSizeGb/maxSizeGb for the region you intend to use).
providerstringYesProvider name: data-crunch, spheron-ai, spheron-es, spheron-ms, or spheron-con
regionstringConditionalRegion for the volume. Required for data-crunch, spheron-ai, spheron-es, spheron-ms, and spheron-con. Spheron MS and Spheron Con have no default region, and a volume can only be mounted by an instance (for Spheron Con, a container) in the same region. Use GET /api/volumes/regions?provider=<id> to list available options per provider.
teamIdstringNoTeam ID (defaults to current team)
deploymentIdstringNoDeployment ID to attach to during creation. Supported for data-crunch, spheron-ai, and spheron-es, not for spheron-con. Attaching a Spheron ES volume to a running instance restarts the VM. Not supported for spheron-ms, or for spheron-ai volumes in Noida and Chennai.
Request body example:
{
  "name": "my-data-volume",
  "sizeInGb": 100,
  "provider": "data-crunch",
  "teamId": "team_id_optional",
  "deploymentId": "deployment_id_optional",
  "note": "For data-crunch: add 'region' (e.g. 'FIN-01'). For spheron-ai: add 'region' (e.g. 'CANADA-1', 'MON1', 'OSL1'). Use GET /api/volumes/regions to discover valid values."
}
Response example:
{
  "id": "volume_internal_id",
  "volumeId": "vol_abc123",
  "name": "my-data-volume",
  "userId": "user_id",
  "teamId": "team_id",
  "providerId": "data-crunch",
  "sizeInGb": 100,
  "usedCapacityBytes": 0,
  "virtualIp": "192.168.100.50",
  "attachedToDeploymentIds": [],
  "attachedToOrderIds": [],
  "status": "available",
  "hourlyRate": 0.0137,
  "lastPriceUpdate": "2026-02-07T10:00:00Z",
  "createdAt": "2026-02-07T10:00:00Z"
}

Get volume details

Get volume details with fresh usage data from the provider.

Method: GET Path: /api/volumes/{volumeId}

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
volumeIdstringYesVolume ID to retrieve
Response example:
{
  "id": "volume_internal_id",
  "volumeId": "vol_abc123",
  "name": "my-data-volume",
  "userId": "user_id",
  "teamId": "team_id",
  "providerId": "data-crunch",
  "sizeInGb": 100,
  "usedCapacityBytes": 52428800000,
  "virtualIp": "192.168.100.50",
  "attachedToDeploymentIds": ["deployment_id_1"],
  "attachedToOrderIds": ["order_123"],
  "status": "attached",
  "hourlyRate": 0.0137,
  "lastPriceUpdate": "2026-02-07T10:00:00Z",
  "createdAt": "2026-02-07T10:00:00Z",
  "lastAttachedAt": "2026-02-07T10:15:00Z"
}

Update volume

Update volume properties. Per-provider rules: data-crunch and spheron-es support size expansion (no shrinking); spheron-ai does not support rename or resize, and a volume in Noida or Chennai rejects both sizeInGb and name. The volume is immutable after creation, so create a new larger volume and migrate your data instead. Spheron MS volumes are immutable and reject all updates after creation. Spheron Con volumes can be renamed and made larger, up to 4096 GB, attached or not.

Method: PATCH Path: /api/volumes/{volumeId}

Headers:
  • Authorization: Bearer <your-api-key>
  • Content-Type: application/json
Parameters:
NameTypeRequiredDescription
volumeIdstringYesVolume ID to update
Request body example:
{
  "name": "renamed-volume",
  "sizeInGb": 200
}
Response example:
{
  "id": "volume_internal_id",
  "volumeId": "vol_abc123",
  "name": "renamed-volume",
  "sizeInGb": 200,
  "virtualIp": "192.168.100.50",
  "hourlyRate": 0.0274,
  "lastPriceUpdate": "2026-02-07T11:00:00Z"
}

Delete volume

Delete a volume. The volume must not be attached to any active deployments. Terminate the instance first, then delete the volume.

Method: DELETE Path: /api/volumes/{volumeId}

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
volumeIdstringYesVolume ID to delete
Response example:
{
  "success": true
}

Attach volume

Attach a volume to a running deployment. Volume and deployment must be from the same provider and region.

Method: POST Path: /api/volumes/{volumeId}/attach

Headers:
  • Authorization: Bearer <your-api-key>
  • Content-Type: application/json
Parameters:
NameTypeRequiredDescription
volumeIdstringYesVolume ID to attach
Request body example:
{
  "deploymentId": "deployment_id_here"
}
Response example:
{
  "id": "volume_internal_id",
  "volumeId": "vol_abc123",
  "status": "attached",
  "virtualIp": "192.168.100.50",
  "attachedToDeploymentIds": ["deployment_id_here"],
  "attachedToOrderIds": ["order_123"],
  "lastAttachedAt": "2026-02-07T12:00:00Z"
}

Detach volume

Detach a volume from a deployment. Volume data is preserved and the volume can be attached to another deployment.

Method: POST Path: /api/volumes/{volumeId}/detach

Headers:
  • Authorization: Bearer <your-api-key>
  • Content-Type: application/json
Parameters:
NameTypeRequiredDescription
volumeIdstringYesVolume ID to detach
Request body example:
{
  "deploymentId": "deployment_id_here"
}
Response example:
{
  "id": "volume_internal_id",
  "volumeId": "vol_abc123",
  "status": "available",
  "virtualIp": "192.168.100.50",
  "attachedToDeploymentIds": [],
  "attachedToOrderIds": [],
  "lastDetachedAt": "2026-02-07T13:00:00Z"
}

Get volume pricing

Get storage pricing per GB per hour for a provider. The rate can vary by region. Pass region to get the rate for the specific region you intend to use; omitting it returns the provider's default rate. Note: GET /api/volumes/regions already embeds hourlyRatePerGb on each region entry, so this endpoint is mainly useful when you don't need the full region list (e.g. lightweight provider-level price lookups).

Method: GET Path: /api/volumes/pricing

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
providerstringNoProvider name: data-crunch, spheron-ai, spheron-es, spheron-ms, or spheron-con. Pass it explicitly to select the provider whose regions you want.
regionstringNoRegion identifier. Pass this when the provider's rate varies by region (e.g. spheron-ai).
Response example:
{
  "provider": "data-crunch",
  "hourlyRatePerGb": 0.000137
}

Get volume regions

Get available regions and volume tiers for a given storage provider. Required before creating data-crunch, spheron-ai, spheron-es, spheron-ms, or spheron-con volumes to pick a valid region value. Each region entry includes minSizeGb, maxSizeGb, and hourlyRatePerGb, so if you're already listing regions you don't need to also call GET /api/volumes/pricing. hasGpuOffers indicates whether any GPU offers exist in that region; the top-level hasAnyOffers flag is true when the provider has any live GPU offers at all.

Method: GET Path: /api/volumes/regions

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
providerstringNoProvider name: data-crunch, spheron-ai, spheron-es, spheron-ms, or spheron-con. Pass it explicitly to select the provider whose regions you want. Unknown values return a 400.

All providers return the same shape. hasGpuOffers is per region; hasAnyOffers is true if at least one region has offers.

Response example (data-crunch):
{
  "hasAnyOffers": true,
  "regions": [
    { "id": "Texas", "name": "Dallas, TX", "country_code": "US", "types": ["NFS"], "hasGpuOffers": true, "minSizeGb": 1, "maxSizeGb": 64000, "hourlyRatePerGb": 0.000137 }
  ]
}
Response example (?provider=data-crunch):
{
  "hasAnyOffers": true,
  "regions": [
    { "id": "FIN-01", "name": "Finland 1", "country_code": "FI", "types": ["NVMe_Shared"], "hasGpuOffers": true, "minSizeGb": 1, "maxSizeGb": 10000, "hourlyRatePerGb": 0.000196 },
    { "id": "FIN-02", "name": "Finland 2", "country_code": "FI", "types": ["NVMe_Shared"], "hasGpuOffers": true, "minSizeGb": 1, "maxSizeGb": 10000, "hourlyRatePerGb": 0.000196 },
    { "id": "FIN-03", "name": "Finland 3", "country_code": "FI", "types": ["NVMe_Shared"], "hasGpuOffers": false, "minSizeGb": 1, "maxSizeGb": 10000, "hourlyRatePerGb": 0.000196 }
  ]
}
Response example (?provider=spheron-ai):

Spheron AI exposes several regions; capacity and rate can vary by region and are returned per region in this response.

{
  "hasAnyOffers": true,
  "regions": [
    { "id": "CANADA-1", "name": "Canada 1", "types": ["Cloud-SSD"], "hasGpuOffers": true, "minSizeGb": 1, "maxSizeGb": 40960, "hourlyRatePerGb": 0.000109 },
    { "id": "NORWAY-1", "name": "Norway 1", "types": ["Cloud-SSD"], "hasGpuOffers": false, "minSizeGb": 1, "maxSizeGb": 40960, "hourlyRatePerGb": 0.000109 },
    { "id": "MON1", "name": "Montreal 1", "country_code": "CA", "types": ["NVME"], "hasGpuOffers": true, "minSizeGb": 1, "maxSizeGb": 51200, "hourlyRatePerGb": 0.000168 },
    { "id": "OSL1", "name": "Oslo 1", "country_code": "NO", "types": ["NVME"], "hasGpuOffers": false, "minSizeGb": 1, "maxSizeGb": 51200, "hourlyRatePerGb": 0.000168 }
  ]
}

Teams

Get balance

Get account balance. Returns all teams by default.

Method: GET Path: /api/balance

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
teamIdstringNoGet balance for a specific team ID only
allbooleanNoSet to 'false' to get current team only (default: true)
Response example:
{
  "teams": [
    {
      "teamId": "team_1",
      "teamName": "My Personal Team",
      "balance": 25.50,
      "isCurrentTeam": true,
      "role": "owner"
    },
    {
      "teamId": "team_2",
      "teamName": "Company Team",
      "balance": 150.75,
      "isCurrentTeam": false,
      "role": "admin"
    }
  ],
  "currency": "USD"
}

List teams

List every team the authenticated caller belongs to, with each team's members, plan, owner, and real-time balance. If the caller has no team yet, a default personal team is created on first call and returned in the response, so this endpoint is safe to call after sign-up without a separate bootstrap step. Use the team id field when passing teamId into deployment, volume, or balance endpoints.

Method: GET Path: /api/teams

Headers:
  • Authorization: Bearer <your-api-key>
Response example:
[
  {
    "id": "team_id_1",
    "name": "My Personal Team",
    "slug": "my-personal-team-1707307200000",
    "ownerId": "user_id",
    "balance": 25.50,
    "members": [
      {
        "userId": "user_id",
        "email": "owner@example.com",
        "name": "Owner Name",
        "role": "owner",
        "joinedAt": "2024-01-15T10:30:00Z"
      }
    ],
    "createdAt": "2024-01-15T10:30:00Z"
  }
]

Get team details

Fetch a single team by ID. The authenticated caller must be a member of the team (owner, admin, or member); otherwise the request is rejected with 403. Returns the same team shape that GET /api/teams returns for entries in its array, with one exception: the stored balance is returned as-is and is NOT recomputed in real time. For an up-to-date balance, use GET /api/balance?teamId=<id> instead.

Method: GET Path: /api/teams/details/{teamId}

Headers:
  • Authorization: Bearer <your-api-key>
Parameters:
NameTypeRequiredDescription
teamIdstringYesTeam ID to fetch. Obtain from GET /api/teams.
Response example:
{
  "id": "team_id",
  "name": "My Personal Team",
  "slug": "my-personal-team-1707307200000",
  "ownerId": "user_id",
  "balance": 25.50,
  "members": [
    {
      "userId": "user_id",
      "email": "owner@example.com",
      "name": "Owner Name",
      "role": "owner",
      "joinedAt": "2024-01-15T10:30:00Z"
    }
  ],
  "createdAt": "2024-01-15T10:30:00Z"
}

Error reference

Every error response is JSON with a stable code to branch on and an error sentence to show a person. Branch on code, never on the text: the wording of error can change, the code does not.

HTTP/1.1 402 Payment Required
X-Request-Id: 3f2b8c1e-5d4a-4c1b-9b7e-2a6f0d8e4c11
{
  "error": "Insufficient credits. You have $0.40 real-time balance but need at least $1.00 for 0.5 hour(s) minimum runtime. Please add credits to continue.",
  "code": "insufficient_credits",
  "legacyCode": "INSUFFICIENT_CREDITS",
  "details": { "requiredAmount": 1, "balance": 0.4 },
  "requestId": "3f2b8c1e-5d4a-4c1b-9b7e-2a6f0d8e4c11",
  "requiredAmount": 1,
  "balance": 0.4
}
FieldAlways presentDescription
errorYesA sentence written for a person. Show it to your user.
codeYesA lowercase snake_case code from the error codes list. Branch on this.
requestIdYesThe same value as the X-Request-Id response header. Quote it when you contact support.
detailsNoFacts about the error: requiredAmount and balance (in dollars), field (the request field at fault), or retryAfter (seconds). A few older validation routes send a list of messages here instead.
legacyCodeNoThe old UPPERCASE spelling of a code that has been renamed. Deprecated: see Renamed codes.

Some endpoints also send fields they sent before codes existed, such as message, success: false, or requiredAmount and balance at the top level. Those fields stay.

Handle an error

  1. Read code and act on it using the error codes table.
  2. For a code your integration does not know, fall back to the HTTP status. New codes may be added; existing codes never change meaning.
  3. On 429, wait details.retryAfter seconds before retrying. See Rate limits.
  4. On 500, 502 or 503, retry with backoff.
  5. On 504, or when the connection drops during a create, start or stop, read the resource before you retry. The action may already have happened.

Every response carries an X-Request-Id header. To trace a request end to end, send your own X-Request-Id (8 to 64 letters, digits, dots, dashes or underscores) and Spheron uses it; otherwise Spheron generates one.

Error codes

Each code lists every HTTP status it is sent with.

General

CodeStatusMeaningWhat to do
bad_request400The request is malformed or a value is not allowed.Fix the request using the error text. Retrying it unchanged fails the same way.
validation_error400A field is missing or has a value that is not allowed. details.field names it when known.Correct the named field and send the request again.
unauthorized401No valid credentials: the API key or session token is missing, invalid, revoked or expired.Send a valid key as Authorization: Bearer <key>. Create a new key in Settings, API Keys if this one stopped working.
payment_required402The action needs more credits than the team has.Add credits, then retry.
forbidden403The credentials are valid but not allowed to do this.Check the account, team or key permissions. Retrying does not help.
not_found404The resource or endpoint does not exist, or you do not have access to it.Check the ID and the path.
conflict409The resource is not in a state that allows this action right now.Read the resource, then retry once its state allows the action.
payload_too_large413The request body is larger than the server accepts.Send a smaller body.
unprocessable422The request is well formed but cannot be carried out.Change the request as the error text says. Retrying it unchanged fails.
rate_limited429Too many requests in the current window.Wait details.retryAfter seconds (also in the Retry-After or RateLimit-Reset header), then retry.
internal_error500Something failed on our side.Retry with backoff. If it keeps failing, contact support with the requestId.
bad_gateway502A provider behind Spheron returned an error.Retry with backoff. Check the resource state first if the request was a create, start or stop.
service_unavailable503The service or a provider is temporarily unavailable.Retry with backoff.
gateway_timeout504A provider behind Spheron did not answer in time. The action may or may not have happened.Check the resource state before retrying.

Sign-in and account

CodeStatusMeaningWhat to do
email_not_verified
was EMAIL_NOT_VERIFIED
403The account email is not verified yet.Verify the email from the link we sent, then retry.
email_unverified403Same as email_not_verified, sent by the inference plan request form.Verify the email from the link we sent, then retry.
email_required
was EMAIL_REQUIRED
400The sign-in provider did not share an email address, and an account needs one.Add a verified email to the sign-in provider account, or sign up another way.
profile_invalid
was PROFILE_INVALID
400The sign-in profile is missing details an account needs.Sign up another way.
login_failed
was LOGIN_FAILED
500The account could not be set up during sign-in.Retry in a minute.
authentication_required401No credentials were sent: the Authorization header is missing or empty.Send Authorization: Bearer <key> with a GPU VM API key, or a signed-in session token from the dashboard.
invalid_api_key401The API key is malformed, unknown, revoked or expired.Check the key, or create a new one in Settings, API Keys.
invalid_token401The dashboard session token is invalid or expired.Sign in again to get a fresh token.
account_restricted403An administrator has restricted this account, so it cannot perform this action.Contact support. Retrying does not help.
user_not_found404The signed-in account has no Spheron profile yet.Sign in to the dashboard once to finish setting up the account, then retry.
team_not_found404The team does not exist, or the account has no team yet.Check the team ID, or list your teams with GET /api/teams.

API keys

CodeStatusMeaningWhat to do
vm_api_blocked403GPU VM API access is turned off for this account.Email info@spheron.ai if you think this is a mistake. Retrying does not help.
wrong_api_key_type403An Inference API key was used on the GPU VM API.Create a GPU VM API key in Settings, API Keys and use that.
api_key_team_mismatch403The key is limited to teams that do not include the one the request names.Name one of the key's teams, or use a key for this team.
api_key_team_route403A team-limited key cannot call this endpoint.Use a key that is not limited to a team.
api_key_team_unavailable403The key's team no longer exists, or the key owner left it.Create a new key in Settings, API Keys.
api_key_limit_reached409The account already has the maximum number of active API keys.Revoke a key you no longer use, then create the new one.
api_key_not_found404No API key with that ID on this account.Check the key ID.
api_key_inactive409The key is already revoked or expired.Nothing to do for this key. Create a new one if you need access.
inference_keys_unavailable403Inference API keys cannot be created right now.Create a GPU VM API key instead.
team_required400A team must be chosen for this key.Send the team ID with the request.
team_access_denied403You are not a member of the team the request names, or of a team a new key would be limited to.Name a team you belong to. List them with GET /api/teams.
invalid_name400The key name is empty or too long.Send a shorter, non-empty name.
invalid_type400The key type is not "inference" or "vm".Send type "vm".
invalid_expiry400The expiry is not a whole number of days in range, or "never".Send a whole number of days in the allowed range, or "never".

Billing

CodeStatusMeaningWhat to do
insufficient_credits
was INSUFFICIENT_CREDITS
402, 400The team balance cannot cover the action. Deploy and start answer 402 with requiredAmount and balance; volume create and resize answer 400.Add credits, then retry. details.requiredAmount and details.balance are in dollars when present.
spend_cap_exceeded422, 402The action would go over the spend cap set on this AI connection. Deploy and start answer 422; an inference request answers 402.Raise the cap on the connection, or have the account owner do the action.
spend_lock_busy409Another launch or start on this AI connection is still in progress.Wait a moment, then retry.

Deployments

CodeStatusMeaningWhat to do
offer_unavailable
was OFFER_UNAVAILABLE
409The offer sold out between listing and deploying, or its capacity is unavailable right now.Fetch GET /api/gpu-offers again and pick another offer or region.
deployment_in_progress409Another deployment for the team is being created right now. Deployments for a team are created one at a time.Wait for it to finish, then retry.
deployment_not_found404No deployment with that ID, or it is not visible to you.Check the ID with GET /api/deployments.
deployment_access_denied403The deployment belongs to a team or user you cannot act for.Use a key or account with access to the deployment's team.
nothing_to_update404The update request changed no field.Send at least one field to change, for example name.
deployment_not_terminable400The instance is in a state that cannot be terminated right now.Read currentStatus, wait for the instance to settle, then retry.
minimum_runtime_not_met400The instance has not run for its minimum runtime yet, so it cannot be terminated.Wait for the time in the error text, then retry.
reserved_instance_protected403Reserved instances can only be terminated by Spheron.Contact support to end a reserved instance.
termination_in_progress409The instance is already being terminated.Nothing to do. Poll the deployment until it shows terminated.
access_token_unavailable404, 502The instance has no sign-in token, or it could not be read from the instance.On 404, stop: this image has no token. On 502, retry in a moment.
instance_not_running409The action needs a running instance, and this one is not running.Wait until the instance is running, then retry.
idempotency_in_progress409A request with this Idempotency-Key is still being processed.Wait, then list instances before retrying with the same key.
idempotency_key_spent409This Idempotency-Key already created an instance that has since been removed.Deploy again with a new key.
idempotency_key_reused422This Idempotency-Key was already used for a different request body.Use a new key for each distinct deployment.
agent_access_unavailable
was AGENT_ACCESS_UNAVAILABLE
400The GPU sandbox cannot be added to this deployment right now.Deploy without the sandbox, or retry later.
agent_access_unsupported
was AGENT_ACCESS_UNSUPPORTED
400The GPU sandbox is not available on this offer.Pick an offer from another provider, or deploy without the sandbox.

Stop, start and restart

CodeStatusMeaningWhat to do
instance_busy409Another stop, start, restart or volume change on this instance is still in progress.Wait a few minutes, read the instance, then retry.
instance_already_starting409The instance is already starting.Nothing to do. Poll the deployment until it is running.
instance_not_ready400, 409The instance is still being set up, or has no instance at the provider yet.Wait until it is running, then retry.
action_not_allowed400The instance's type or state does not allow this action (for example stopping a reserved or already stopped instance).Read the error text and the instance state; retrying unchanged fails the same way.
action_not_supported400The provider does not support this action, or it does not apply to this instance.Use another action, or deploy on a provider that supports it.
above_max_price409The live spot price is above the max price set on this instance.Raise or remove the max price, then start it.
max_price_not_exceeded409The live price is no longer above the max price, so the instance was not stopped.Nothing to do.
provider_start_failed503The provider could not start the instance right now. It is still stopped and billed at the stopped rate.Retry later.
provider_action_failed502The provider refused or failed the stop or restart.Read the error text; retry later if it does not name a cause you can fix.
provider_timeout504The provider is slow to finish the action. The request went through.Do not repeat it. Read the instance in a minute.

SSH keys

CodeStatusMeaningWhat to do
ssh_key_not_found404No SSH key with that ID.Check the ID with GET /api/ssh-keys.
ssh_key_access_denied403The SSH key belongs to a team or user you cannot act for.Use a key or account with access to the SSH key's team.
ssh_key_invalid400The public key is not a valid SSH public key, or its name is not allowed.Fix the key or name as the error text says. Paste the whole public key line, for example ssh-ed25519 AAAA... you@host.

Volumes

CodeStatusMeaningWhat to do
volume_not_found404No volume with that ID, or it is not visible to you.Check the ID with GET /api/volumes.
volume_access_denied403The volume belongs to a team you cannot act for.Use a key or account with access to the volume's team.
volume_attached400The volume is attached to a live instance, so it cannot be deleted.Detach it or terminate the instance first.
volume_attach_at_create_only400This provider attaches volumes only when an instance is created.Pass the volume in volumeIds when you create the deployment.
volume_detach_not_supported400This provider cannot detach a volume from a running instance.Terminate the instance to release the volume.
volume_attached_to_ended_instance
was VOLUME_ATTACHED_TO_ENDED_INSTANCE
400The volume is still recorded on an instance that has ended.Delete it with ?force=true.
volume_stale_attachment
was VOLUME_STALE_ATTACHMENT
409The provider still lists the volume as attached to an instance that is gone.Delete it with ?force=true to remove it and stop its billing.

Providers and pricing

CodeStatusMeaningWhat to do
provider_required400The provider query parameter is missing.Pass a provider brand ID, for example provider=spheron-ai.
invalid_provider400The provider is unknown, or is an internal ID instead of a brand ID.Use a provider ID from GET /api/providers.
pricing_unavailable400, 502Pricing is not available for this provider or instance.On 400 pick a provider that sells volumes. On 502 retry in a moment.

Reservations

CodeStatusMeaningWhat to do
reservation_access_required
was RESERVATION_ACCESS_REQUIRED
403Cluster bidding is invite only, and this account is not on the list.Email info@spheron.ai to request access. Retrying does not help.

Renamed codes

Codes are lowercase snake_case. The codes below first shipped in UPPERCASE. They now send the lowercase spelling in code and the old spelling in legacyCode, so an integration can still read the old spelling while it switches. Update your integration to read code: legacyCode is deprecated and will be removed after notice in the changelog.

Old spelling (now in legacyCode)code
EMAIL_NOT_VERIFIEDemail_not_verified
EMAIL_REQUIREDemail_required
PROFILE_INVALIDprofile_invalid
LOGIN_FAILEDlogin_failed
INSUFFICIENT_CREDITSinsufficient_credits
OFFER_UNAVAILABLEoffer_unavailable
AGENT_ACCESS_UNAVAILABLEagent_access_unavailable
AGENT_ACCESS_UNSUPPORTEDagent_access_unsupported
VOLUME_ATTACHED_TO_ENDED_INSTANCEvolume_attached_to_ended_instance
VOLUME_STALE_ATTACHMENTvolume_stale_attachment
RESERVATION_ACCESS_REQUIREDreservation_access_required

Every other code, including vm_api_blocked, idempotency_in_progress and the api_key_* codes, keeps its spelling.

Status values

Deployment statuses

deploying: Instance is being provisioned. Usually takes 30-60 seconds.

running: Instance is active and accessible via SSH.

stopped: Instance is parked. The GPU is released, the disk and its contents are preserved, and billing runs at stoppedHourlyRate. A stopped instance is part of the live set, so ?status=active includes it. Start it with the resume endpoint.

failed: Deployment failed. Check error details in the deployment object. A deployment that ends before it ever reaches running is recorded as failed, whether you terminate it, the provider removes it, or it runs out of credit. It was never charged. When the platform records a reason for this case, it reads: "This instance never reached running state, so it was never charged. Please try again."

terminated: Instance has been terminated by the user and is no longer accessible.

terminated-provider: Instance was reclaimed or stopped by the provider (e.g., a Spot instance was interrupted). No further action is needed.

Status lifecycle

deploying → running ⇄ stopped → terminated
    ↓            ↓
  failed   terminated-provider
Transitions:
  • deploying to running: Successful deployment
  • deploying to failed: Deployment error, or the instance was terminated before it reached running
  • running to stopped: Stopped by the user, or parked by the provider
  • stopped to running: Started again by the user
  • running or stopped to terminated: Manual termination by user
  • running to terminated-provider: Provider reclaimed the instance (Spot interruption)
  • Any status can transition to failed if errors occur

A restart does not change the status. The instance stays running throughout, and reboot-status reports whether the reboot is still in flight.

Rate limits

All API endpoints are rate limited. Most limits count requests per IP address over a 15-minute window. Deployment creation and instance actions are counted per user, as the table below shows.

Rate limit headers

Responses from rate limited endpoints include these headers. Read them instead of hard-coding a limit: the numbers can change.

RateLimit-Limit: 1000
RateLimit-Remaining: 994
RateLimit-Reset: 412
RateLimit-Policy: 1000;w=900
HeaderMeaning
RateLimit-LimitRequests allowed in the current window.
RateLimit-RemainingRequests left in the current window.
RateLimit-ResetSeconds until the window resets.
RateLimit-PolicyThe limit and the window length in seconds.
Retry-AfterOn a 429 only: seconds to wait before retrying.

Browser clients can read all of these headers, and X-Request-Id, through CORS.

Rate limited endpoints

EndpointsScopeNotes
GET /api/providers, GET /api/gpu-offersPer IP addressAuthentication optional. Authenticated callers see team discounts.
POST /api/deploymentsPer userA lower limit over 15 minutes, plus a burst limit per minute. Enterprise customers can ask for a higher limit.
POST /api/deployments/{deploymentId}/pause, /resume, /restartPer user and instance10 calls per 5 minutes for one instance.
Every other endpointPer IP addressThe general limit, over 15 minutes.

Rate limit response

When a limit is exceeded, the API answers 429 with code rate_limited. details.retryAfter is the number of seconds left in the window. The top-level retryAfter, kept for older integrations, is the length of the whole window on most limits, and the time left on the stop, start and restart limit.

{
  "error": "Too many requests",
  "message": "Too many requests from this IP, please try again later.",
  "retryAfter": 900,
  "code": "rate_limited",
  "details": { "retryAfter": 412 },
  "requestId": "b9e21398-336c-43d1-b117-7bc07fa464b8"
}

Wait details.retryAfter seconds, then retry. Never loop deployment creation on a 429.

Custom rate limits (enterprise)

Enterprise customers can request a higher deployment creation limit, or none. Contact info@spheron.ai.

Security

Best practices

Treat API keys like passwords:

  • Never expose keys in client-side code
  • Do not commit keys to version control
  • Use environment variables for credentials
  • Rotate keys regularly

Key management:

  • Generate keys from dashboard settings
  • Set expiry dates for keys
  • Revoke compromised keys immediately
  • Use separate keys for different environments

Connection security:

  • All API requests must use HTTPS
  • Verify SSL certificates
  • Do not disable certificate validation

For comprehensive security guidelines, see Security best practices.

Common workflows

Complete deployment flow

# 1. Check available GPU offers
curl -H "Authorization: Bearer <your-api-key>" \
  "https://app.spheron.ai/api/gpu-offers?search=rtx-4090&limit=5"
 
# 2. Add SSH key (if not already added)
curl -X POST "https://app.spheron.ai/api/ssh-keys" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Key", "publicKey": "ssh-rsa AAA..."}'
 
# 3. Deploy instance
curl -X POST "https://app.spheron.ai/api/deployments" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "spheron-ai",
    "offerId": "rtx-4090-spheron-ai-1",
    "gpuType": "rtx-4090",
    "gpuCount": 1,
    "region": "us-east-1",
    "operatingSystem": "ubuntu-22.04",
    "instanceType": "DEDICATED",
    "sshKeyId": "your_ssh_key_id"
  }'
 
# 4. Monitor deployment status
curl -H "Authorization: Bearer <your-api-key>" \
  "https://app.spheron.ai/api/deployments/deployment_id"
 
# 5. Terminate when done
curl -X DELETE "https://app.spheron.ai/api/deployments/deployment_id" \
  -H "Authorization: Bearer <your-api-key>"

What's next