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
- Authentication
- Endpoints
- Error reference
- Status values
- Rate limits
- Security
- Common workflows
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/providers3. 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.aiAuthentication
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
[
"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
Authorization: Bearer<your-api-key>(optional)
| Name | Type | Required | Description |
|---|---|---|---|
page | number | No | Page number (default: 1) |
limit | number | No | Items per page (default: 10) |
search | string | No | Search term for GPU models |
sortBy | string | No | Sort field (default: popularity) |
sortOrder | string | No | asc or desc (default: asc) |
instanceType | string | No | Filter by type: 'SPOT', 'DEDICATED', or 'CLUSTER' (case-insensitive) |
computeType | string | No | Filter 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. |
{
"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.
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.
gpuTypeis"CPU"anddisplayNameis"CPU NODE".- Every entry in
offers[]reportsgpuCount: 0andgpu_memory: 0. - Size is described by
vcpusandmemoryrather than a GPU model. clustersholds the region,os_optionsholds the deployable images, andofferIdis what a deployment takes.
{
"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
}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.
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.
providerisspheron-con, and the group'sgpuTypeends in_CONTAINER(A40_PCIE_CONTAINER, orCPU_CONTAINERfor CPU containers). A GPU sold both ways has two groups, such asA40_PCIEandA40_PCIE_CONTAINER.os_optionsis["Container"],supportsCloudInitisfalse,instanceTypeisDEDICATEDandminimumRuntimeMinutesis20, the same minimum as a virtual machine.extras.containerdescribes what comes with it:compute(gpuorcpu),containerDiskGb(100 on a GPU container), and on a GPU containervolumeGbandvolumeMountPath(a 500 GB volume at/workspace),stoppedHourlyUsd(what the container bills per hour while stopped) andvolumeHourlyUsd(what the built-in volume adds to the running price, dropped when you pass your own volume involumeIds).specsAreMinimums: truemeansvcpusandmemoryare what you get at least.extras.volumes_supportedsays whether a Spheron Con volume can be used in that offer's region.extras.lifecycle.canPauseistruefor GPU containers andfalsefor 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 }
}
}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.
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 field | Description |
|---|---|
image | Required. A public image from Docker Hub, GHCR, Quay or another registry. A private or nonexistent image returns 400 before anything is created or charged. |
command | Replaces the image's own start command, as one shell-style line. |
ports | Up to 9 ports. Each gets its own public port. Port 22 is added for SSH when you send an SSH key. |
env | Up 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. |
operatingSystemis not needed and is set to the image.cloudInitis refused.- An SSH key is optional for a container. Without one, the container runs with no SSH, port 22 stays closed and no
sshCommandis returned. Images that run as a non-root user, such as Jupyter, have no SSH either way and are used through their ports. volumeIdstakes at most one Spheron Con volume, in the same region. It is mounted at/workspacein 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.
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
failureReasonthat says so, and billed only for the minutes it ran. - A container that ran and then keeps exiting stays
runningwithcontainerState.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
Authorization: Bearer<your-api-key>Content-Type: application/json
| Name | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | Provider 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. |
offerId | string | Yes | Unique offer ID from the GPU offers endpoint |
gpuType | string | Yes | GPU type identifier (e.g., rtx-4090, h100). Send CPU for a CPU Node offer. |
gpuCount | number | Yes | Number of GPUs to deploy. Send 0 for a CPU Node offer. |
region | string | Yes | Deployment region or cluster. Send the offer's clusters value back unchanged. |
operatingSystem | string | Yes | Operating system (e.g., ubuntu-20.04, ubuntu-22.04). Not needed for a container, where it is set to container.image. |
container | object | Conditional | Required for a container offer and refused for a virtual machine: image, and optionally command, ports and env. |
instanceType | string | Yes | Instance type: 'SPOT', 'DEDICATED', or 'CLUSTER' (case-insensitive) |
sshKeyId | string | Conditional | SSH 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_key | string | Conditional | Inline SSH public key content. Send this or sshKeyId. A temporary key is created for this deployment. |
teamId | string | No | Team ID for team deployments (optional) |
name | string | No | Optional name for the deployment |
cloudInit | object | No | Cloud-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. |
volumeIds | string[] | No | IDs 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. |
maxHourlyRate | number | No | Max 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. |
{
"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"]
}{
"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"
}| Status | Code | Meaning | What to do |
|---|---|---|---|
400 | validation_error | A 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 cloudInit | Read error, which names the field (and details.field when set). Fix the request; retrying it unchanged fails again. |
400 | agent_access_unsupported, agent_access_unavailable | The Agent GPU Sandbox cannot be added to this offer, or is unavailable right now | Deploy without the sandbox, or pick an offer from another provider. |
402 | insufficient_credits | Your team balance cannot cover the minimum runtime of this offer. details.requiredAmount and details.balance give the amounts in dollars | Add credits, then retry. |
409 | offer_unavailable | The offer sold out between listing and deploying, or its cloud is temporarily unavailable | Refresh GET /api/gpu-offers and pick another offer or region. |
409 | deployment_in_progress | Another deployment for your team is being created right now | Wait for it to finish, then retry. |
422 | spend_cap_exceeded | Through an AI connection: the deployment would go over the connection's spend cap | Raise 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
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
userId | string | No | Filter by user ID (must be your own) |
teamId | string | No | Filter by team ID |
status | string | No | Filter by status: active (running/deploying/stopped), inactive (terminated/failed), or a specific status value: running, deploying, stopped, terminated, failed |
[
{
"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}
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
deploymentId | string | Yes | Deployment ID |
{
"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, ornullfor no limit. See Set max price.pauseInitiator:"price-cap"when the instance was stopped because the spot price rose abovemaxHourlyRate. Thestatusisstopped.stoppedHourlyRate: What a stopped instance is billed. While it is stopped,hourlyRatekeeps following the live spot price, so compare it withmaxHourlyRateto 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}
Authorization: Bearer<your-api-key>Content-Type: application/json
| Name | Type | Required | Description |
|---|---|---|---|
deploymentId | string | Yes | Deployment ID to update |
{
"name": "Renamed Instance"
}{
"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}
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
deploymentId | string | Yes | Deployment ID to terminate |
{
"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
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
deploymentId | string | Yes | Deployment ID to check |
{
"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
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
deploymentId | string | Yes | Deployment to quote |
{
"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
Authorization: Bearer<your-api-key>
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
Authorization: Bearer<your-api-key>
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
Authorization: Bearer<your-api-key>Content-Type: application/json
| Name | Type | Required | Description |
|---|---|---|---|
deploymentId | string | Yes | Deployment to update (path) |
maxHourlyRate | number or null | Yes | Max 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. |
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
Authorization: Bearer<your-api-key>
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
Authorization: Bearer<your-api-key>
{
"restarting": true,
"since": "2026-09-08T10:31:04Z"
}restarting is false and since is absent when no reboot is in flight.
Lifecycle errors
| Status | Code | Meaning | What to do |
|---|---|---|---|
400 | action_not_allowed, action_not_supported | The instance's type or status does not allow this action, or the provider cannot perform it | Read error for the specific limit. Do not retry. |
400 | instance_not_ready | The instance has no machine at the provider yet | Wait until it is running, then retry. |
402 | insufficient_credits | A start needs at least 30 minutes of the instance's running rate in balance. details.requiredAmount and details.balance give the amounts | Add credits, then start it. |
403 | deployment_access_denied | The deployment belongs to another user or team | Check the deployment ID. |
404 | deployment_not_found | No deployment with that ID | Check the deployment ID. |
409 | instance_busy, instance_already_starting | Another change is still in flight on this instance, such as a volume change or an earlier restart, or it is already starting | Wait a few minutes and retry. |
409 | above_max_price | On a start: a spot instance's hourlyRate is above its maxHourlyRate | Wait for the price to fall, or change the max price. |
422 | spend_cap_exceeded | Through an AI connection: the start would go over the connection's spend cap | Raise the cap, or start it from the dashboard. |
502 | provider_action_failed, pricing_unavailable | The provider refused the action, or its stopped rate could not be determined | Read error. Nothing was changed. |
503 | provider_start_failed | A start was refused, usually because the machine type is out of stock, or for a GPU container because the GPU on its machine is taken | Retry later. The instance and its data are intact. |
504 | provider_timeout | The provider is taking longer than usual | The 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
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
userId | string | No | Filter by user ID (must be your own) |
teamId | string | No | Filter by team ID |
[
{
"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
Authorization: Bearer<your-api-key>Content-Type: application/json
{
"name": "My SSH Key",
"publicKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... user@hostname",
"teamId": "team_id_optional"
}{
"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}
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | SSH key ID |
{
"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}
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | SSH key ID to delete |
{
"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
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
teamId | string | No | Filter by team ID (defaults to current team) |
page | number | No | Page number (default: 1) |
limit | number | No | Items per page (default: 20) |
status | string | No | Filter by status: 'available', 'attached', 'deleting', or 'deleted' |
{
"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
Authorization: Bearer<your-api-key>Content-Type: application/json
| Name | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Volume name (lowercase alphanumeric with hyphens/underscores, max 60 chars) |
sizeInGb | number | Yes | Volume 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). |
provider | string | Yes | Provider name: data-crunch, spheron-ai, spheron-es, spheron-ms, or spheron-con |
region | string | Conditional | Region 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. |
teamId | string | No | Team ID (defaults to current team) |
deploymentId | string | No | Deployment 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. |
{
"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."
}{
"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}
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
volumeId | string | Yes | Volume ID to retrieve |
{
"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}
Authorization: Bearer<your-api-key>Content-Type: application/json
| Name | Type | Required | Description |
|---|---|---|---|
volumeId | string | Yes | Volume ID to update |
{
"name": "renamed-volume",
"sizeInGb": 200
}{
"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}
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
volumeId | string | Yes | Volume ID to delete |
{
"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
Authorization: Bearer<your-api-key>Content-Type: application/json
| Name | Type | Required | Description |
|---|---|---|---|
volumeId | string | Yes | Volume ID to attach |
{
"deploymentId": "deployment_id_here"
}{
"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
Authorization: Bearer<your-api-key>Content-Type: application/json
| Name | Type | Required | Description |
|---|---|---|---|
volumeId | string | Yes | Volume ID to detach |
{
"deploymentId": "deployment_id_here"
}{
"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
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
provider | string | No | Provider name: data-crunch, spheron-ai, spheron-es, spheron-ms, or spheron-con. Pass it explicitly to select the provider whose regions you want. |
region | string | No | Region identifier. Pass this when the provider's rate varies by region (e.g. spheron-ai). |
{
"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
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
provider | string | No | Provider 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.
data-crunch):
{
"hasAnyOffers": true,
"regions": [
{ "id": "Texas", "name": "Dallas, TX", "country_code": "US", "types": ["NFS"], "hasGpuOffers": true, "minSizeGb": 1, "maxSizeGb": 64000, "hourlyRatePerGb": 0.000137 }
]
}?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 }
]
}?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
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
teamId | string | No | Get balance for a specific team ID only |
all | boolean | No | Set to 'false' to get current team only (default: true) |
{
"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
Authorization: Bearer<your-api-key>
[
{
"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}
Authorization: Bearer<your-api-key>
| Name | Type | Required | Description |
|---|---|---|---|
teamId | string | Yes | Team ID to fetch. Obtain from GET /api/teams. |
{
"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
}| Field | Always present | Description |
|---|---|---|
error | Yes | A sentence written for a person. Show it to your user. |
code | Yes | A lowercase snake_case code from the error codes list. Branch on this. |
requestId | Yes | The same value as the X-Request-Id response header. Quote it when you contact support. |
details | No | Facts 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. |
legacyCode | No | The 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
- Read
codeand act on it using the error codes table. - For a code your integration does not know, fall back to the HTTP status. New codes may be added; existing codes never change meaning.
- On
429, waitdetails.retryAfterseconds before retrying. See Rate limits. - On
500,502or503, retry with backoff. - 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
| Code | Status | Meaning | What to do |
|---|---|---|---|
bad_request | 400 | The request is malformed or a value is not allowed. | Fix the request using the error text. Retrying it unchanged fails the same way. |
validation_error | 400 | A 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. |
unauthorized | 401 | No 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_required | 402 | The action needs more credits than the team has. | Add credits, then retry. |
forbidden | 403 | The credentials are valid but not allowed to do this. | Check the account, team or key permissions. Retrying does not help. |
not_found | 404 | The resource or endpoint does not exist, or you do not have access to it. | Check the ID and the path. |
conflict | 409 | The 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_large | 413 | The request body is larger than the server accepts. | Send a smaller body. |
unprocessable | 422 | The request is well formed but cannot be carried out. | Change the request as the error text says. Retrying it unchanged fails. |
rate_limited | 429 | Too many requests in the current window. | Wait details.retryAfter seconds (also in the Retry-After or RateLimit-Reset header), then retry. |
internal_error | 500 | Something failed on our side. | Retry with backoff. If it keeps failing, contact support with the requestId. |
bad_gateway | 502 | A provider behind Spheron returned an error. | Retry with backoff. Check the resource state first if the request was a create, start or stop. |
service_unavailable | 503 | The service or a provider is temporarily unavailable. | Retry with backoff. |
gateway_timeout | 504 | A 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
| Code | Status | Meaning | What to do |
|---|---|---|---|
email_not_verifiedwas EMAIL_NOT_VERIFIED | 403 | The account email is not verified yet. | Verify the email from the link we sent, then retry. |
email_unverified | 403 | Same as email_not_verified, sent by the inference plan request form. | Verify the email from the link we sent, then retry. |
email_requiredwas EMAIL_REQUIRED | 400 | The 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_invalidwas PROFILE_INVALID | 400 | The sign-in profile is missing details an account needs. | Sign up another way. |
login_failedwas LOGIN_FAILED | 500 | The account could not be set up during sign-in. | Retry in a minute. |
authentication_required | 401 | No 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_key | 401 | The API key is malformed, unknown, revoked or expired. | Check the key, or create a new one in Settings, API Keys. |
invalid_token | 401 | The dashboard session token is invalid or expired. | Sign in again to get a fresh token. |
account_restricted | 403 | An administrator has restricted this account, so it cannot perform this action. | Contact support. Retrying does not help. |
user_not_found | 404 | The signed-in account has no Spheron profile yet. | Sign in to the dashboard once to finish setting up the account, then retry. |
team_not_found | 404 | The 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
| Code | Status | Meaning | What to do |
|---|---|---|---|
vm_api_blocked | 403 | GPU 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_type | 403 | An 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_mismatch | 403 | The 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_route | 403 | A team-limited key cannot call this endpoint. | Use a key that is not limited to a team. |
api_key_team_unavailable | 403 | The key's team no longer exists, or the key owner left it. | Create a new key in Settings, API Keys. |
api_key_limit_reached | 409 | The 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_found | 404 | No API key with that ID on this account. | Check the key ID. |
api_key_inactive | 409 | The key is already revoked or expired. | Nothing to do for this key. Create a new one if you need access. |
inference_keys_unavailable | 403 | Inference API keys cannot be created right now. | Create a GPU VM API key instead. |
team_required | 400 | A team must be chosen for this key. | Send the team ID with the request. |
team_access_denied | 403 | You 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_name | 400 | The key name is empty or too long. | Send a shorter, non-empty name. |
invalid_type | 400 | The key type is not "inference" or "vm". | Send type "vm". |
invalid_expiry | 400 | The 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
| Code | Status | Meaning | What to do |
|---|---|---|---|
insufficient_creditswas INSUFFICIENT_CREDITS | 402, 400 | The 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_exceeded | 422, 402 | The 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_busy | 409 | Another launch or start on this AI connection is still in progress. | Wait a moment, then retry. |
Deployments
| Code | Status | Meaning | What to do |
|---|---|---|---|
offer_unavailablewas OFFER_UNAVAILABLE | 409 | The 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_progress | 409 | Another 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_found | 404 | No deployment with that ID, or it is not visible to you. | Check the ID with GET /api/deployments. |
deployment_access_denied | 403 | The 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_update | 404 | The update request changed no field. | Send at least one field to change, for example name. |
deployment_not_terminable | 400 | The instance is in a state that cannot be terminated right now. | Read currentStatus, wait for the instance to settle, then retry. |
minimum_runtime_not_met | 400 | The 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_protected | 403 | Reserved instances can only be terminated by Spheron. | Contact support to end a reserved instance. |
termination_in_progress | 409 | The instance is already being terminated. | Nothing to do. Poll the deployment until it shows terminated. |
access_token_unavailable | 404, 502 | The 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_running | 409 | The action needs a running instance, and this one is not running. | Wait until the instance is running, then retry. |
idempotency_in_progress | 409 | A request with this Idempotency-Key is still being processed. | Wait, then list instances before retrying with the same key. |
idempotency_key_spent | 409 | This Idempotency-Key already created an instance that has since been removed. | Deploy again with a new key. |
idempotency_key_reused | 422 | This Idempotency-Key was already used for a different request body. | Use a new key for each distinct deployment. |
agent_access_unavailablewas AGENT_ACCESS_UNAVAILABLE | 400 | The GPU sandbox cannot be added to this deployment right now. | Deploy without the sandbox, or retry later. |
agent_access_unsupportedwas AGENT_ACCESS_UNSUPPORTED | 400 | The GPU sandbox is not available on this offer. | Pick an offer from another provider, or deploy without the sandbox. |
Stop, start and restart
| Code | Status | Meaning | What to do |
|---|---|---|---|
instance_busy | 409 | Another stop, start, restart or volume change on this instance is still in progress. | Wait a few minutes, read the instance, then retry. |
instance_already_starting | 409 | The instance is already starting. | Nothing to do. Poll the deployment until it is running. |
instance_not_ready | 400, 409 | The instance is still being set up, or has no instance at the provider yet. | Wait until it is running, then retry. |
action_not_allowed | 400 | The 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_supported | 400 | The 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_price | 409 | The live spot price is above the max price set on this instance. | Raise or remove the max price, then start it. |
max_price_not_exceeded | 409 | The live price is no longer above the max price, so the instance was not stopped. | Nothing to do. |
provider_start_failed | 503 | The provider could not start the instance right now. It is still stopped and billed at the stopped rate. | Retry later. |
provider_action_failed | 502 | The 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_timeout | 504 | The provider is slow to finish the action. The request went through. | Do not repeat it. Read the instance in a minute. |
SSH keys
| Code | Status | Meaning | What to do |
|---|---|---|---|
ssh_key_not_found | 404 | No SSH key with that ID. | Check the ID with GET /api/ssh-keys. |
ssh_key_access_denied | 403 | The 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_invalid | 400 | The 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
| Code | Status | Meaning | What to do |
|---|---|---|---|
volume_not_found | 404 | No volume with that ID, or it is not visible to you. | Check the ID with GET /api/volumes. |
volume_access_denied | 403 | The volume belongs to a team you cannot act for. | Use a key or account with access to the volume's team. |
volume_attached | 400 | The volume is attached to a live instance, so it cannot be deleted. | Detach it or terminate the instance first. |
volume_attach_at_create_only | 400 | This provider attaches volumes only when an instance is created. | Pass the volume in volumeIds when you create the deployment. |
volume_detach_not_supported | 400 | This provider cannot detach a volume from a running instance. | Terminate the instance to release the volume. |
volume_attached_to_ended_instancewas VOLUME_ATTACHED_TO_ENDED_INSTANCE | 400 | The volume is still recorded on an instance that has ended. | Delete it with ?force=true. |
volume_stale_attachmentwas VOLUME_STALE_ATTACHMENT | 409 | The 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
| Code | Status | Meaning | What to do |
|---|---|---|---|
provider_required | 400 | The provider query parameter is missing. | Pass a provider brand ID, for example provider=spheron-ai. |
invalid_provider | 400 | The provider is unknown, or is an internal ID instead of a brand ID. | Use a provider ID from GET /api/providers. |
pricing_unavailable | 400, 502 | Pricing is not available for this provider or instance. | On 400 pick a provider that sells volumes. On 502 retry in a moment. |
Reservations
| Code | Status | Meaning | What to do |
|---|---|---|---|
reservation_access_requiredwas RESERVATION_ACCESS_REQUIRED | 403 | Cluster 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_VERIFIED | email_not_verified |
EMAIL_REQUIRED | email_required |
PROFILE_INVALID | profile_invalid |
LOGIN_FAILED | login_failed |
INSUFFICIENT_CREDITS | insufficient_credits |
OFFER_UNAVAILABLE | offer_unavailable |
AGENT_ACCESS_UNAVAILABLE | agent_access_unavailable |
AGENT_ACCESS_UNSUPPORTED | agent_access_unsupported |
VOLUME_ATTACHED_TO_ENDED_INSTANCE | volume_attached_to_ended_instance |
VOLUME_STALE_ATTACHMENT | volume_stale_attachment |
RESERVATION_ACCESS_REQUIRED | reservation_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-providerdeployingtorunning: Successful deploymentdeployingtofailed: Deployment error, or the instance was terminated before it reachedrunningrunningtostopped: Stopped by the user, or parked by the providerstoppedtorunning: Started again by the userrunningorstoppedtoterminated: Manual termination by userrunningtoterminated-provider: Provider reclaimed the instance (Spot interruption)- Any status can transition to
failedif 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| Header | Meaning |
|---|---|
RateLimit-Limit | Requests allowed in the current window. |
RateLimit-Remaining | Requests left in the current window. |
RateLimit-Reset | Seconds until the window resets. |
RateLimit-Policy | The limit and the window length in seconds. |
Retry-After | On 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
| Endpoints | Scope | Notes |
|---|---|---|
GET /api/providers, GET /api/gpu-offers | Per IP address | Authentication optional. Authenticated callers see team discounts. |
POST /api/deployments | Per user | A 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, /restart | Per user and instance | 10 calls per 5 minutes for one instance. |
| Every other endpoint | Per IP address | The 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
- Getting Started: Account setup and first deployment
- Security best practices: API key management and security
- Connecting to instances: SSH and networking setup
- Billing: Credits and usage monitoring