OpenRelay VMs API

GPU virtual machines: lifecycle, disks, SSH access, and console links.

OpenAPI Specification

openrelay-vms-api-openapi.yml Raw ↑
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