Create a VM
Creates a GPU or CPU VM from the requested shape, image, disk and SSH keys and returns it as a VmSummary; a valid request with no free capacity answers 503.
Required permission
Requires the compute.create permission. See Permissions for the roles and API key permissions that include it.
Command line
The orl CLI wraps this endpoint:
Guided workflow: orl deploy
orl deploy <name> [flags]orl vms create --name <name> --ssh-key-ids <ssh-key-ids>apiKeyAuthorizationBearer <token>OpenRelay API key. Send it as Authorization: Bearer or_… (legacy vl_… keys still work).
orgId*stringIdempotency-Key?stringMakes a retried create safe. Choose one value per create you mean to make, 1 to 512 bytes (bytes, not characters), such as a UUID, and send it again on every retry of that create. The first request with a key runs, and a 2xx answer is kept for 24 hours: a repeat with the same key and the same body gets that answer back, with the same status and the header Idempotent-Replayed: true, and creates nothing. A batch create compares the body byte for byte; a VM or pod create compares the fields it decodes, so whitespace and key order do not count. A repeat while the first request is still running answers 409; repeat it a second later. The same key with a different body answers 422. An answer other than a 2xx is not kept, so a retry after a 4xx or a 5xx runs again. A 2xx too large to keep holds the key without it: every repeat answers 409 and creates nothing. Keys are scoped to the organization and the operation.
1 <= lengthapplication/json- body
name*stringresourceSize?stringCPU-only VM machine size (the tier picker). Sending it alongside gpuCount is rejected with RESOURCE_SIZE_NOT_APPLICABLE: a GPU VM is sized from the host capacity its GPUs come with, not from this table.
guestMemMb?integerGuest RAM for a GPU VM, in MiB. Omit for auto, which takes the tightest node that can give a full per-GPU share. At least 65% of the VRAM the VM is placed against (the catalog's floorPerGpu; a smaller request is raised to it), and at most some node's per-GPU share times gpuCount: a request above every node's share is rejected with VM_SHAPE_NOT_PLACEABLE, and one inside a share that no node has free right now with INSUFFICIENT_HOST_CAPACITY. Starting the VM after a stop, or re-dispatching it after its job is lost, starts it on its own node at no more than that node's current share (see requestedGuestMemMb).
1 <= valuetier?stringgpuModelId?stringlength <= 256gpuCount?integerimageUrl?stringcontainerPort?integerpreferredRegions?array<string>allowFallback?booleanenvVars?healthChecks?array<string>healthCheckPath?stringhealthCheckPort?integerdiskSizeGb?integerBoot disk in GB. Omit to get the default, which for a GPU VM is 100 GB per GPU. That default is not a floor: a smaller size is accepted down to the platform minimum (minDiskGb on GET /v1/gpu-availability). The ceiling is the node's per-GPU disk share times gpuCount (perGpu.diskGb on its offer), so a one-GPU VM cannot take a whole node's VM dir, and the size must also be free on that node right now.
containerCommand?array<string>privileged?booleankernelModules?array<string>templateId?stringsshKeyIds*array<string>Org SSH key ids injected into the guest. At least one is required and every id must name a live key of this org: the data-plane gateway authorizes guest SSH against exactly this list, so a VM created without one has no SSH access.
1 <= items <= 64public?booleantrue = open endpoint (no data-plane auth); default false requires an org API key
cloudInit?stringStartup script run inside the guest the first time this VM boots. Must begin with a shebang (e.g. "#!/bin/bash"); a raw cloud-config document is rejected. Runs once per disk, not once per boot: a stop/start keeps the disk and does not re-run it, while a VM that lands on a fresh disk does. It runs as root once the network and your SSH keys are up, and its output goes to the VM's logs. A script that fails does not stop the VM from starting or from being billed, so check the logs rather than assuming it succeeded.
length <= 16384nodeId?stringPlace this VM on a specific node. Required to use capacity reserved to your org; omit to use the public pool. There is no fallback: if the node has no room for this GPU count the request is refused rather than placed elsewhere. A node you have no rights on reads as not found.
Created
application/json- response
id*stringorganizationId*stringname*stringstatus*stringdeploying, running, stopped, failed, terminated, or interrupted; a row can also carry the queued status pending. interrupted means it served and is not serving now: it keeps its node, GPU and disk, and is not billed. A VM is interrupted while its node is offline and resumes when the node returns; a pod is interrupted for that and for losing its supervisor or its job on a live node, which the platform recovers by re-submitting it. After 7 days interrupted a VM is stopped (disk kept) and a pod is deleted.
statusReason?stringCause of the current status. The machine-readable values are "node_lost" (the host went offline and is expected back, so the disk is preserved), "node_removed" (the host was decommissioned, so the disk is gone and a restart deploys fresh elsewhere), "vm_exited", "user_stopped", "user_terminated", "user_restarted", "spot_reclaimed_by_capacity_owner", "spot_preempted_for_reservation_tenant", "auto_stopped_insufficient_balance", "auto_stopped_account_suspended", "auto_stopped_organization_deleted", the prefix "container failed to start: " followed by the container runtime's own message, and on a running workload the prefix "node leaving at " followed by the RFC 3339 time its host is removed, when the workload ends with node_removed. Anything else is free text for a human to read.
gpuModelId?stringgpuCount?integerendpointUrl?stringvolumeGb?integerpods only: size in GB of the pod's persistent /workspace volume.
public?booleantrue = open endpoint (no data-plane auth); false = require an org API key
createdAt?stringprovisioningStage?stringresourceSize?stringtier?stringdiskSizeGb?integergpuModelName?stringgpuManufacturer?stringGPU vendor from the catalog, the same value as GpuModel.manufacturer (NVIDIA, AMD); absent on a CPU-only row
pricePerHourCents?integerserviceType?stringmanaged-service tag; empty for a plain VM
managedBy?stringplatform owner of this row ("serving" = inference serving replica, read-only on customer surfaces); empty for a customer-launched VM
capabilityTier?stringcompute-fabric capability tier (e.g. "pod"); empty for a plain VM
computeClass?stringcompute-fabric purchase option (on_demand; pods created before the rename carry the legacy reserved or interruptible values); empty for a plain VM
imageUrl?stringtenant container image (pods)
curl -X POST 'https://api.openrelay.inc/v1/orgs/string/vms/create' \ -H 'Content-Type: application/json' \ -d '{ "name": "cuda-devbox", "gpuModelId": "rtx-4090", "gpuCount": 1, "imageUrl": "ghcr.io/acme/cuda-devbox:latest", "diskSizeGb": 100, "sshKeyIds": [ "key_2f9c1a" ], "envVars": { "HF_TOKEN": "hf_xxxxxxxx" }}'{ "id": "string", "organizationId": "string", "name": "string", "status": "string", "statusReason": "string", "gpuModelId": "string", "gpuCount": 0, "endpointUrl": "string", "volumeGb": 0, "public": true, "createdAt": "string", "provisioningStage": "string", "resourceSize": "string", "tier": "string", "diskSizeGb": 0, "gpuModelName": "string", "gpuManufacturer": "string", "pricePerHourCents": 0, "serviceType": "string", "managedBy": "string", "capabilityTier": "string", "computeClass": "string", "imageUrl": "string"}Pod GPU metrics GET
Returns utilization, memory, power and temperature time series for each of a pod's GPUs over the selected range, sampled on the host that runs the pod. The telemetry is host-side (nvidia-smi on NVIDIA hosts, amd-smi on AMD), sampled on the node that runs the pod and returned as one continuous time grid. Every series and every GPU in a response shares that grid: the value at index i of any values array was measured at start plus i times stepSeconds, and a null means the node reported no sample at that point. The window ends now and covers only the periods this pod actually held the GPUs it is reported against, so a card that ran another customer's pod earlier never contributes a sample here. A GPU attached to a VM is passed through to the guest, so the host cannot read it. That case answers 200 with an accelerator status of unavailable and an empty gpus list rather than an error, and so do a pod that has not started yet, a pod that was not running in the selected range, and a metrics store that cannot be reached: the accelerator object is the answer, and the client renders its detail sentence. A host that stopped reporting while the pod is still running answers degraded with the history up to the last sample still attached. This operation is exposed as an agent tool by default, in the same class as a pod's burn rate: "how busy were my GPUs" is a read-only question about a resource the caller already owns.
Stop a VM POST
Stops the workload and releases its GPU. A VM keeps its disk; a pod keeps its /workspace volume and its node, and can be started again with restart. While stopping VMs is switched off for the organization, a VM stop answers 409 STOP_DISABLED: terminate the VM, or leave it running. Pods are not affected.