OpenRelay VMs API
GPU virtual machines: lifecycle, disks, SSH access, and console links.
GPU virtual machines: lifecycle, disks, SSH access, and console links.
openapi: 3.1.0
info:
description: 'The OpenRelay control-plane REST API. Deploy GPU VMs and inference clusters, manage organizations and billing, and automate your infrastructure. All requests are authenticated with an API key (`vl_…`) unless noted otherwise.
'
title: OpenRelay Account VMs API
version: 0.1.0
servers:
- description: Production
url: https://api.openrelay.inc
- description: Beta
url: https://api.beta.openrelay.inc
- description: Local development
url: http://localhost:8083
tags:
- description: 'GPU virtual machines: lifecycle, disks, SSH access, and console links.'
name: VMs
paths:
/v1/orgs/{orgId}/vms:
get:
operationId: listOrgVms
parameters:
- in: path
name: orgId
required: true
schema:
type: string
- in: query
name: limit
required: false
schema:
type: integer
- in: query
name: cursor
required: false
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/VmPage'
description: Page of VMs
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
security:
- apiKey: []
summary: List an org's VMs (cursor-paginated)
tags:
- VMs
x-openrelay-mcp:
default: true
/v1/orgs/{orgId}/vms/create:
post:
operationId: createVm
parameters:
- in: path
name: orgId
required: true
schema:
type: string
requestBody:
content:
application/json:
example:
diskSizeGb: 100
envVars:
HF_TOKEN: hf_xxxxxxxx
gpuCount: 1
gpuModelId: rtx-4090
imageUrl: ghcr.io/acme/cuda-devbox:latest
name: cuda-devbox
sshKeyIds:
- key_2f9c1a
schema:
$ref: '#/components/schemas/CreateVmRequest'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/VmSummary'
description: Created
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
security:
- apiKey: []
summary: Create a VM
tags:
- VMs
x-openrelay-mcp:
default: true
/v1/vms/{id}:
get:
operationId: getVm
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/VmSummary'
description: The VM
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
security:
- apiKey: []
summary: Get a VM by id
tags:
- VMs
/v1/vms/{id}/burn:
get:
description: 'Every VM bills at one bundled hourly rate (per-GPU catalog price times GPU count for GPU VMs, a flat size rate for CPU VMs); disk is included, never billed separately. The rate is locked when the usage session opens, so this endpoint reads it from the open usage record when one exists (rateSource=locked) and only falls back to the current catalog when nothing is accruing (rateSource=catalog).
'
operationId: getVmBurn
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
example:
accruedSessionCents: 1028
autoStopFloorCents: 50
diskBilled: false
gpuCount: 3
orgAccruedMonthCents: 2120
orgAvailableCents: 10430
orgBurnPerHourCents: 414
perGpuCents: 137
pricePerHourCents: 411
projectedRunwayHours: 25.07
rateBasis: gpu
rateSource: locked
sessionStartedAt: '2026-07-18T09:12:00Z'
vmId: 4f6b9a1e-9f1c-4c58-a6a3-1f2f0f6f7a10
schema:
$ref: '#/components/schemas/VmBurn'
description: The VM's burn breakdown
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
security:
- apiKey: []
summary: Live burn rate, session cost so far, and org runway for a VM
tags:
- VMs
x-openrelay-mcp:
default: true
/v1/vms/{id}/detail:
get:
operationId: getVmDetail
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/VmDetail'
description: The VM detail
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
security:
- apiKey: []
summary: Get a VM with its GPU/node/template info and price
tags:
- VMs
x-openrelay-mcp:
default: true
/v1/vms/{id}/reboot:
post:
description: 'Beta: the reboot request is validated and accepted, but the in-guest reboot is not yet executed by the data plane.'
operationId: rebootVm
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'204':
description: Rebooted
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
security:
- apiKey: []
summary: Reboot a VM
tags:
- VMs
/v1/vms/{id}/resize-disk:
post:
description: 'Beta: the new disk size is validated and recorded, but the disk is not yet resized on the node.'
operationId: resizeVmDisk
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ResizeDiskRequest'
required: true
responses:
'204':
description: Resized
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
security:
- apiKey: []
summary: Resize a stopped VM's disk
tags:
- VMs
/v1/vms/{id}/restart:
post:
operationId: restartVm
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/VmSummary'
description: OK
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
security:
- apiKey: []
summary: Restart a VM
tags:
- VMs
/v1/vms/{id}/ssh-keys:
get:
operationId: getVmSshKeys
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
items:
$ref: '#/components/schemas/VmSshKeyItem'
type: array
description: OK
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
security:
- apiKey: []
summary: SSH keys attached to a VM + the org's keys
tags:
- VMs
post:
operationId: attachVmSshKey
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AttachSshKeyRequest'
required: true
responses:
'204':
description: Attached
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
security:
- apiKey: []
summary: Attach an org SSH key to a VM
tags:
- VMs
/v1/vms/{id}/stop:
post:
operationId: stopVm
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/VmSummary'
description: OK
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
security:
- apiKey: []
summary: Stop a VM
tags:
- VMs
x-openrelay-mcp:
default: true
/v1/vms/{id}/telegram-link:
get:
operationId: getVmTelegramLink
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/TelegramLinkResult'
description: OK
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
security:
- apiKey: []
summary: Get a VM's embedded Telegram link
tags:
- VMs
/v1/vms/{id}/terminate:
post:
operationId: terminateVm
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/VmSummary'
description: OK
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
security:
- apiKey: []
summary: Terminate a VM
tags:
- VMs
x-openrelay-mcp:
destructiveHint: true
/v1/vms/{id}/visibility:
post:
operationId: setVmVisibility
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SetVmVisibilityRequest'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/VmSummary'
description: OK
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
security:
- apiKey: []
summary: Set a VM's endpoint visibility
tags:
- VMs
x-openrelay-mcp:
default: true
components:
schemas:
VmDetail:
properties:
containerPort:
type: integer
createdAt:
type: string
diskSizeGb:
type: integer
diskTotalBytes:
format: int64
type: integer
diskUsedBytes:
format: int64
type: integer
endpointUrl:
type: string
gpuCount:
type: integer
gpuInfo:
$ref: '#/components/schemas/VmGpuInfo'
gpuModelId:
type: string
healthy:
type: boolean
id:
type: string
imageUrl:
type: string
internalIp:
type: string
name:
type: string
nodeId:
type: string
nodeInfo:
$ref: '#/components/schemas/VmNodeInfo'
organizationId:
type: string
osType:
type: string
persistentDiskPath:
type: string
pricePerHourCents:
description: bundled hourly rate. When the VM has an open usage session this is the rate LOCKED at session open (what settlement charges); otherwise the current catalog rate for the VM's shape.
type: integer
provisioningStage:
type: string
public:
description: true = open endpoint (no data-plane auth); false = require an org API key
type: boolean
rdpPassword:
type: string
rdpPort:
type: integer
resourceSize:
type: string
serviceType:
description: managed-service tag, e.g. "paseo" for a Coding Server; empty for a plain VM
type: string
sshCommand:
description: ready-to-paste ssh command (ssh <handle>@<host>)
type: string
sshHandle:
description: RunPod-style SSH handle (first label of endpointUrl); used as the SSH username
type: string
sshHost:
description: data-plane SSH gateway host, e.g. ssh.run.beta.openrelay.inc
type: string
sshPort:
type: integer
sshReady:
description: true once the VM is running and its data-plane route exists
type: boolean
sshUser:
description: login user the gateway lands as inside the VM (e.g. ubuntu)
type: string
startedAt:
type: string
status:
type: string
statusReason:
description: human-readable cause for failed/stuck states
type: string
templateInfo:
$ref: '#/components/schemas/VmTemplateInfo'
tier:
type: string
updatedAt:
type: string
required:
- id
- organizationId
- name
- status
- gpuCount
- resourceSize
- tier
- diskSizeGb
- healthy
- createdAt
- updatedAt
type: object
CreateVmRequest:
properties:
allowFallback:
type: boolean
containerCommand:
items:
type: string
type: array
containerPort:
type: integer
diskSizeGb:
type: integer
envVars:
additionalProperties:
type: string
type: object
gpuCount:
type: integer
gpuModelId:
type: string
healthCheckPath:
type: string
healthCheckPort:
type: integer
healthChecks:
items:
type: string
type: array
imageUrl:
type: string
kernelModules:
items:
type: string
type: array
name:
type: string
preferredRegions:
items:
type: string
type: array
privileged:
type: boolean
public:
description: true = open endpoint (no data-plane auth); default false requires an org API key
type: boolean
registryCredentialId:
type: string
resourceSize:
type: string
serviceType:
description: managed-service tag, e.g. "paseo" for a Coding Server; empty for a plain VM
type: string
sshKeyIds:
items:
type: string
type: array
templateId:
type: string
tier:
type: string
required:
- name
type: object
Error:
properties:
code:
type: string
error:
type: string
required:
- error
type: object
VmPage:
properties:
items:
items:
$ref: '#/components/schemas/VmSummary'
type: array
nextCursor:
type: string
required:
- items
type: object
VmSummary:
properties:
createdAt:
type: string
diskSizeGb:
type: integer
endpointUrl:
type: string
gpuCount:
type: integer
gpuModelId:
type: string
gpuModelName:
type: string
id:
type: string
name:
type: string
organizationId:
type: string
pricePerHourCents:
type: integer
provisioningStage:
type: string
public:
description: true = open endpoint (no data-plane auth); false = require an org API key
type: boolean
resourceSize:
type: string
serviceType:
description: managed-service tag, e.g. "paseo" for a Coding Server; empty for a plain VM
type: string
status:
type: string
statusReason:
description: human-readable cause for failed/stuck states
type: string
tier:
type: string
required:
- id
- organizationId
- name
- status
type: object
TelegramLinkResult:
properties:
createdAt:
type: string
linkedAt:
type: string
linkingCode:
type: string
present:
type: boolean
telegramUserId:
format: int64
type: integer
telegramUsername:
type: string
required:
- present
type: object
VmTemplateInfo:
properties:
name:
type: string
osType:
type: string
osVersion:
type: string
required:
- name
- osType
type: object
VmSshKeyItem:
properties:
attached:
type: boolean
crossOrg:
type: boolean
fingerprint:
type: string
id:
type: string
name:
type: string
orgName:
nullable: true
type: string
required:
- id
- name
- fingerprint
- attached
- crossOrg
type: object
VmBurn:
properties:
accruedSessionCents:
description: cost accrued by the open session so far (session span times the locked rate, the same span math settlement uses); omitted when no session is open
type: integer
autoStopFloorCents:
description: available-balance floor (cents) below which the org is auto-stopped
type: integer
diskBilled:
description: 'always false today: disk is included in the bundled hourly rate and never billed as a separate line item'
type: boolean
gpuCount:
description: set when rateBasis is gpu
type: integer
orgAccruedMonthCents:
description: the org's current-month accrued, un-settled usage across all workloads
type: integer
orgAvailableCents:
description: spendable balance = ledger balance minus orgAccruedMonthCents (can be negative)
type: integer
orgBurnPerHourCents:
description: sum of the locked rates of every open usage record in the org
type: integer
perGpuCents:
description: pricePerHourCents / gpuCount; set when rateBasis is gpu
type: integer
pricePerHourCents:
description: 'the VM''s bundled hourly rate: the open usage record''s locked rate when a session is accruing, else the current catalog rate for the VM''s shape'
type: integer
projectedRunwayHours:
description: (orgAvailableCents - autoStopFloorCents) / orgBurnPerHourCents; null when orgBurnPerHourCents is 0, clamped to 0 when available is already at or below the floor
format: double
nullable: true
type: number
rateBasis:
description: gpu = per-GPU catalog price times gpuCount; cpu = flat per-resource-size rate
enum:
- gpu
- cpu
type: string
rateSource:
description: locked = read from the open usage record (exactly what settlement will charge); catalog = no open session, the rate that would apply on next start
enum:
- locked
- catalog
type: string
sessionStartedAt:
description: start of the open usage session; omitted when none
type: string
vmId:
type: string
required:
- vmId
- pricePerHourCents
- rateSource
- rateBasis
- diskBilled
- orgAccruedMonthCents
- orgAvailableCents
- orgBurnPerHourCents
- projectedRunwayHours
- autoStopFloorCents
type: object
ResizeDiskRequest:
properties:
newSizeGb:
type: integer
required:
- newSizeGb
type: object
VmNodeInfo:
properties:
hostname:
type: string
location:
type: string
required:
- hostname
type: object
AttachSshKeyRequest:
properties:
sshKeyId:
type: string
required:
- sshKeyId
type: object
VmGpuInfo:
properties:
name:
type: string
pricePerHourCents:
description: current catalog per-GPU-hour rate for this model/tier (informational; the billed rate is VmDetail.pricePerHourCents)
type: integer
vramGb:
type: integer
required:
- name
- vramGb
type: object
SetVmVisibilityRequest:
properties:
public:
description: true = open endpoint (no data-plane auth); false = require an org API key
type: boolean
required:
- public
type: object
responses:
NotFound:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Resource not found
Forbidden:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: API key lacks the required scope
Unauthorized:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Missing or invalid API key
Conflict:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: The request conflicts with existing state
BadRequest:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: The request is invalid
securitySchemes:
apiKey:
description: 'OpenRelay API key. Send it as `Authorization: Bearer vl_…`.'
scheme: bearer
type: http