openapi: 3.0.3 info: title: Raff Cloud Platform API description: | REST API for managing cloud infrastructure on the Raff Cloud Platform. ## Authentication Most endpoints require authentication via API key. Catalog endpoints under `/api/v1/public/` are open and require no authentication. ### API Key Authentication Include your API key in the `X-API-Key` header: ``` curl -H "X-API-Key: YOUR_API_KEY" https://api.rafftechnologies.com/api/v1/vms ``` ## Catalog Use the public catalog endpoints to discover available regions, OS templates, and pricing plans before creating resources: - `GET /api/v1/public/regions` — list available regions - `GET /api/v1/public/templates` — list OS templates (use the `id` as `template_id` when creating a VM) - `GET /api/v1/public/pricing/vm` — list VM pricing plans (use the `id` as `pricing_id` when creating a VM) - `GET /api/v1/public/pricing/volume` — volume storage pricing - `GET /api/v1/public/pricing/snapshot` — snapshot storage pricing - `GET /api/v1/public/pricing/backup` — backup storage pricing - `GET /api/v1/public/pricing/ip` — IP address pricing version: 1.0.0 contact: name: Raff Technologies url: https://rafftechnologies.com servers: - url: https://api.raffcomputing.com description: Development - url: https://api.rafftechnologies.com description: Production security: - ApiKeyAuth: [] tags: - name: Catalog description: Discover available regions, OS templates, and pricing plans. No authentication required. - name: Health description: Health check endpoints - name: Projects description: Organize resources into projects for billing and access control - name: Virtual Machines description: Create, manage, and control virtual machines - name: Networking description: Attach and detach VPCs, floating IPs, and security groups to VM network interfaces paths: /health: get: tags: - Health summary: Health check description: Returns the health status of the API gateway. No authentication required. operationId: healthCheck security: [] responses: '200': description: Service is healthy content: application/json: schema: type: object properties: status: type: string example: healthy /api/v1/public/regions: get: tags: - Catalog summary: List regions description: Returns all active data center regions. operationId: listRegions security: [] responses: '200': description: List of regions content: application/json: schema: type: object properties: success: type: boolean example: true regions: type: array items: $ref: '#/components/schemas/Region' /api/v1/public/templates: get: tags: - Catalog summary: List OS templates description: | Returns all public OS templates available for VM creation. Windows templates are only available for premium VM types. operationId: listTemplates security: [] parameters: - name: category in: query description: Filter by template category schema: type: string enum: [os, marketplace] - name: vm_type in: query description: Filter by VM type. When `standard`, Windows templates are excluded. schema: type: string enum: [standard, premium] - name: region in: query description: Filter by region schema: type: string enum: [us-east] responses: '200': description: List of templates content: application/json: schema: type: object properties: success: type: boolean example: true data: type: array items: $ref: '#/components/schemas/Template' total: type: integer description: Total number of templates returned example: 12 '400': $ref: '#/components/responses/BadRequest' /api/v1/public/pricing/vm: get: tags: - Catalog summary: List VM pricing plans description: Returns VM pricing plans. Use the plan `id` as `pricing_id` when creating a VM. operationId: listVMPricing security: [] parameters: - name: type in: query description: Filter by VM type schema: type: string enum: [standard, premium] - name: region in: query description: Filter by region schema: type: string enum: [us-east] responses: '200': description: List of VM pricing plans content: application/json: schema: type: object properties: success: type: boolean example: true plans: type: array items: $ref: '#/components/schemas/VMPricingPlan' '400': $ref: '#/components/responses/BadRequest' /api/v1/public/pricing/volume: get: tags: - Catalog summary: Get volume pricing description: Returns block storage volume pricing per GB. operationId: listVolumePricing security: [] parameters: - name: region in: query description: Filter by region schema: type: string enum: [us-east] responses: '200': description: Volume pricing content: application/json: schema: type: object properties: success: type: boolean example: true pricing: $ref: '#/components/schemas/StoragePricing' '404': $ref: '#/components/responses/NotFound' /api/v1/public/pricing/snapshot: get: tags: - Catalog summary: Get snapshot pricing description: Returns snapshot storage pricing per GB. operationId: listSnapshotPricing security: [] parameters: - name: region in: query description: Filter by region schema: type: string enum: [us-east] responses: '200': description: Snapshot pricing content: application/json: schema: type: object properties: success: type: boolean example: true pricing: $ref: '#/components/schemas/StoragePricing' '404': $ref: '#/components/responses/NotFound' /api/v1/public/pricing/backup: get: tags: - Catalog summary: Get backup pricing description: Returns backup storage pricing per GB. operationId: listBackupPricing security: [] parameters: - name: region in: query description: Filter by region schema: type: string enum: [us-east] responses: '200': description: Backup pricing content: application/json: schema: type: object properties: success: type: boolean example: true pricing: $ref: '#/components/schemas/StoragePricing' '404': $ref: '#/components/responses/NotFound' /api/v1/public/pricing/ip: get: tags: - Catalog summary: Get IP address pricing description: Returns pricing for IPv4 and IPv6 addresses. operationId: listIPPricing security: [] responses: '200': description: IP pricing content: application/json: schema: type: object properties: success: type: boolean example: true pricing: $ref: '#/components/schemas/IPPricing' '404': $ref: '#/components/responses/NotFound' /api/v1/projects: get: tags: - Projects summary: List projects description: List all projects for the authenticated account. operationId: listProjects parameters: - name: limit in: query description: Maximum number of projects to return schema: type: integer default: 20 - name: offset in: query description: Number of projects to skip for pagination schema: type: integer default: 0 responses: '200': description: List of projects content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/Project' total: type: integer description: Total number of projects '401': $ref: '#/components/responses/Unauthorized' post: tags: - Projects summary: Create project description: Create a new project within the authenticated account. operationId: createProject requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateProjectRequest' responses: '201': description: Project created successfully content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Project' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/projects/{id}: get: tags: - Projects summary: Get project description: Get details of a specific project. operationId: getProject parameters: - $ref: '#/components/parameters/ProjectIDPath' responses: '200': description: Project details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Project' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' put: tags: - Projects summary: Update project description: Update an existing project's settings. operationId: updateProject parameters: - $ref: '#/components/parameters/ProjectIDPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateProjectRequest' responses: '200': description: Project updated successfully content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Project' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' delete: tags: - Projects summary: Delete project description: Delete a project. The project must not contain any active resources. operationId: deleteProject parameters: - $ref: '#/components/parameters/ProjectIDPath' responses: '200': description: Project deleted successfully content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms: get: tags: - Virtual Machines summary: List VMs description: List all virtual machines for the authenticated account. operationId: listVMs parameters: - name: project_id in: query description: Filter by project ID schema: type: string format: uuid - name: region in: query description: Filter by region schema: type: string enum: [us-east] - name: status in: query description: | Filter by VM status. Values: `initiating` (queued/setup), `provisioning` (being created), `booting` (starting), `active` (running), `passive` (stopped), `finalizing` (shutting down), `failure` (failed). schema: type: string enum: [active, passive, provisioning, booting, initiating, finalizing, failure] - name: limit in: query description: Maximum number of VMs to return schema: type: integer default: 20 - name: offset in: query description: Number of VMs to skip for pagination schema: type: integer default: 0 responses: '200': description: List of VMs content: application/json: schema: type: object properties: success: type: boolean description: Whether the request was successful data: type: array items: $ref: '#/components/schemas/VM' total: type: integer description: Total number of VMs matching the query '401': $ref: '#/components/responses/Unauthorized' post: tags: - Virtual Machines summary: Create VM description: | Create a new virtual machine with a chosen OS template, compute plan, and region. The VM is automatically assigned to a VPC for private networking. ## Authentication How your VM is accessed depends on the OS: - **Linux** — provide `ssh_keys`, `password`, or both. At least one is required. - **Windows** — `password` is required. SSH keys are not supported. ## Extra Storage Set `extra_storage` (GB) to attach an additional block volume. On Linux, choose the filesystem with `extra_storage_type` (defaults to `ext4`). Windows volumes are automatically formatted as NTFS. ## Backups - **Daily** — set `backup_type` to `daily`. Runs every day at `backup_time` (defaults to `8am`). - **Weekly** — set `backup_type` to `weekly` with a `backup_date` (e.g. `Saturday`). Runs at `backup_time`. Omit `backup_type` or set it to `none` to skip backups. ## VPC Network Each VM is attached to a VPC for private networking: 1. **Use existing** — set `vpc_id` to join an existing VPC. 2. **Create new** — set `vpc_name` and `vpc_cidr` to create a custom VPC. 3. **Auto-create** (default) — leave all VPC fields empty. A VPC named `vpc-{vm-name}` is created automatically. ## Billing Checks Before provisioning, the API validates: - Account billing status (not banned, no failed payments) - Active payment method exists operationId: createVM parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateVMRequest' responses: '201': description: VM created successfully content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/VM' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '403': $ref: '#/components/responses/BillingValidationFailed' /api/v1/vms/{id}: get: tags: - Virtual Machines summary: Get VM description: Get details of a specific virtual machine. operationId: getVM parameters: - $ref: '#/components/parameters/VMIDPath' responses: '200': description: VM details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/VM' '404': $ref: '#/components/responses/NotFound' delete: tags: - Virtual Machines summary: Delete VM description: | Permanently delete a virtual machine and release its resources. ## Attached Volumes Control what happens to volumes attached to the VM with `volume_action`: - **`detach`** (default) — volumes are detached and kept. They remain billable and can be re-attached to another VM. - **`delete`** — volumes are permanently deleted along with the VM. ## VPC Cleanup Set `delete_vpc` to `true` to also delete the VM's associated VPC. The VPC is only deleted if no other VMs are using it. ## Billing If the VM has an active subscription, the remaining prepaid balance is refunded pro-rata (hourly precision) to your account balance. operationId: deleteVM parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/DeleteVMRequest' responses: '200': description: VM deleted successfully content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/bulk: delete: tags: - Virtual Machines summary: Bulk Delete VMs description: | Delete up to 50 virtual machines in a single request. Each VM is processed independently — if some deletions fail, others still succeed. The response includes per-VM results so you can identify and retry failures. ## Attached Volumes Control what happens to volumes attached to the VMs with `volume_action`: - **`delete`** (default) — volumes are permanently deleted along with the VM. - **`detach`** — volumes are detached and kept. They remain billable and can be re-attached to another VM. ## VPC Cleanup Set `delete_vpc` to `true` (default) to also delete each VM's associated VPC. A VPC is only deleted if no other VMs are using it. ## Billing For VMs with active subscriptions, the remaining prepaid balance is refunded pro-rata (hourly precision) to your account balance. operationId: deleteVMsBulk parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DeleteVMsBulkRequest' responses: '200': description: Bulk deletion results content: application/json: schema: $ref: '#/components/schemas/BulkDeleteVMsResult' '400': $ref: '#/components/responses/MissingProjectID' /api/v1/vms/{id}/start: post: tags: - Virtual Machines summary: Start VM description: Start a stopped virtual machine. operationId: startVM parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: VM started content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/stop: post: tags: - Virtual Machines summary: Stop VM description: Stop a running virtual machine. The VM will retain its resources and can be started again. operationId: stopVM parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: VM stopped content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/reboot: post: tags: - Virtual Machines summary: Reboot VM description: Reboot a running virtual machine. operationId: rebootVM parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: VM rebooted content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/rename: patch: tags: - Virtual Machines summary: Rename VM description: Rename a virtual machine. operationId: renameVM parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RenameVMRequest' responses: '200': description: VM renamed content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/reset-password: post: tags: - Virtual Machines summary: Reset VM password description: Reset the root/admin password of a virtual machine. The new password will be emailed to the account owner. operationId: resetVMPassword parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Password reset initiated content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/reinstall: post: tags: - Virtual Machines summary: Reinstall VM description: Reinstall a virtual machine with a new OS template. This will destroy all data on the VM. operationId: reinstallVM parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ReinstallVMRequest' responses: '200': description: VM reinstall initiated content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/factory-reset: post: tags: - Virtual Machines summary: Factory reset VM description: Factory reset a virtual machine to its original state. This will destroy all data on the VM and restore it to the original template. operationId: factoryResetVM parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Factory reset initiated content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/resize: post: tags: - Virtual Machines summary: Resize VM operationId: resizeVM description: | Resize a virtual machine's CPU and RAM by selecting a new pricing plan. The VM must be stopped before resizing. ## Billing For subscription VMs, the price difference is calculated pro-rata for the remaining billing period: - **Upgrade** — the difference is deducted from credits, then account balance. If your balance is insufficient, the request fails with `402`. - **Downgrade** — the pro-rata credit is added to your account balance. The new subscription price takes effect immediately. parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ResizeVMRequest' responses: '200': description: VM resized content: application/json: schema: $ref: '#/components/schemas/ResizeResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/disk/resize: post: tags: - Virtual Machines summary: Resize VM Disk operationId: resizeVMDisk description: | Increase the primary disk of a virtual machine. The VM must be stopped. Disk size can only be increased. ## Billing For subscription VMs, the storage cost difference is calculated pro-rata and deducted from credits, then account balance. Returns `402` if insufficient balance. parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ResizeVMDiskRequest' responses: '200': description: VM disk resized content: application/json: schema: $ref: '#/components/schemas/ResizeResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/reboot-hard: post: tags: - Virtual Machines summary: Hard reboot VM description: | Force an immediate power-off and restart of a virtual machine, **without** a graceful shutdown. ## When to use Use this only when a soft reboot (`POST /api/v1/vms/{id}/reboot`) does not work — for example, the VM is hung and not responding to OS-level signals. ## Risks - **Unsaved data may be lost.** Filesystems and applications cannot flush state before power-off. - **Filesystem corruption is possible.** Hard power-off skips clean unmount. Prefer the standard reboot whenever the VM is responsive. operationId: hardRebootVM parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Hard reboot initiated content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/save-image: post: tags: - Virtual Machines summary: Save VM disk as custom image description: | Save a VM's disk (live state or a snapshot) as a reusable custom OS image. The new image becomes available alongside the public OS templates and can be passed as `template_id` when creating a new VM. ## Sources Pick what to capture with `disk_id` and `snapshot_id`: - `disk_id: 0` — the OS disk - `disk_id: 1+` — an attached volume - `snapshot_id: -1` — capture the **current live disk state** (stop the VM first for a consistent image) - `snapshot_id: ` — capture from a specific saved snapshot operationId: saveVMDiskAsImage parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SaveImageRequest' responses: '201': description: Image creation initiated content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/VMImage' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vpcs: get: tags: - Networking summary: List VPCs description: | List virtual private clouds (VPCs). Pass `X-Project-ID` to scope to a specific project. Omit it to list all VPCs you have access to across the account. operationId: listVPCs parameters: - name: X-Project-ID in: header required: false description: Project to scope the list to. Omit to list across all accessible projects. schema: type: string format: uuid responses: '200': description: List of VPCs content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/VPC' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Networking summary: Create VPC description: | Create a new virtual private cloud. Pick a `cidr` block that doesn't overlap with any of your existing VPCs. ## CIDR planning Use `GET /api/v1/vpcs/cidr-suggestions` to get suggested non-overlapping CIDR blocks if you don't have one in mind. operationId: createVPC parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateVPCRequest' responses: '201': description: VPC created content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/VPC' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/BillingValidationFailed' /api/v1/vpcs/cidr-suggestions: get: tags: - Networking summary: Suggest non-overlapping CIDR blocks description: | Returns suggested CIDR blocks that don't overlap with the account's existing VPCs. Use one of these as `cidr` when creating a new VPC. operationId: listVPCCIDRSuggestions parameters: - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: CIDR suggestions content: application/json: schema: $ref: '#/components/schemas/CIDRSuggestionsResponse' '401': $ref: '#/components/responses/Unauthorized' /api/v1/vpcs/{id}: get: tags: - Networking summary: Get VPC description: Get details of a single VPC. operationId: getVPC parameters: - $ref: '#/components/parameters/VPCIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: VPC details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/VPC' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' put: tags: - Networking summary: Update VPC description: Update a VPC's name or description. CIDR cannot be changed after creation. operationId: updateVPC parameters: - $ref: '#/components/parameters/VPCIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateVPCRequest' responses: '200': description: VPC updated content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/VPC' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' delete: tags: - Networking summary: Delete VPC description: | Delete a VPC. The VPC must not have any VMs attached — detach them first via `DELETE /api/v1/vms/{id}/vpc/{nic_id}`. operationId: deleteVPC parameters: - $ref: '#/components/parameters/VPCIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: VPC deleted content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/ips: get: tags: - Networking summary: List floating IPs description: | List the account's floating (public) IP addresses, including reserved IPs and IPs currently attached to VMs. Filter to just reserved IPs with `?reserved=true`. operationId: listIPs parameters: - $ref: '#/components/parameters/ProjectIDHeader' - name: reserved in: query required: false description: When `true`, return only reserved IPs (not currently attached to a VM). schema: type: boolean - name: status in: query required: false description: | Filter by IP status: - `free` — IP is in the pool. Combined with `reserved=true` it means held for the account but not attached to a VM. - `in-use` — currently attached to a VM. schema: type: string enum: [free, in-use] - name: limit in: query schema: type: integer default: 20 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of floating IPs content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/FloatingIP' total: type: integer '401': $ref: '#/components/responses/Unauthorized' /api/v1/ips/reserve: post: tags: - Networking summary: Reserve a floating IP description: | Reserve a floating public IP. The IP is allocated immediately and held for your account until you release it. ## Billing Reserved IPs are billed. For subscription accounts the IP price is charged from the account balance up front for the chosen `billing_period` (default monthly): - **Insufficient balance** for the subscription returns `402`. Top up the balance and retry. For pay-as-you-go (PAYG) accounts no upfront charge happens — usage accrues hourly. operationId: reserveIP parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/ReserveIPRequest' responses: '201': description: IP reserved content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/FloatingIP' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '403': $ref: '#/components/responses/BillingValidationFailed' /api/v1/ips/{id}: get: tags: - Networking summary: Get floating IP description: Get details of a single floating IP. operationId: getIP parameters: - $ref: '#/components/parameters/IPIDPath' responses: '200': description: IP details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/FloatingIP' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/ips/{id}/reserve: delete: tags: - Networking summary: Release a reserved IP description: | Release a reserved IP back to the pool. ## Billing For subscription IPs the remaining prepaid balance is refunded pro-rata (hourly precision) to your account balance. For PAYG IPs the IP simply stops accruing further hourly usage — no refund is needed. The IP must not be attached to a VM. Detach it first via `DELETE /api/v1/vms/{id}/ip/{nic_id}`. operationId: releaseIP parameters: - $ref: '#/components/parameters/IPIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: IP released content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/ips/{id}/change: post: tags: - Networking summary: Change reserved IP description: | Swap a reserved IP for a different one from the available pool. The subscription stays the same — no extra charge, no refund. Useful if the current IP is blacklisted somewhere. The IP must be reserved (not currently attached to a VM). operationId: changeIP parameters: - $ref: '#/components/parameters/IPIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: IP changed content: application/json: schema: type: object properties: success: type: boolean old_ip: $ref: '#/components/schemas/FloatingIP' new_ip: $ref: '#/components/schemas/FloatingIP' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/security-groups: get: tags: - Networking summary: List security groups description: | List the account's security groups. Pass `X-Project-ID` to scope to one project, or omit it to list across all accessible projects. operationId: listSecurityGroups parameters: - name: X-Project-ID in: header required: false schema: type: string format: uuid responses: '200': description: List of security groups content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/SecurityGroup' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Networking summary: Create security group description: | Create a new security group with a set of inbound/outbound rules. Optionally seed from a template (`template_id` from `GET /api/v1/security-groups/templates`) — its rules are copied as the starting set, then merged with any explicit `rules` you pass. operationId: createSecurityGroup parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateSecurityGroupRequest' responses: '201': description: Security group created content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SecurityGroup' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/security-groups/templates: get: tags: - Networking summary: List security group templates description: | Pre-built security group templates customers can clone when creating a new group (e.g. "Web server", "SSH only", "Database"). Pass the chosen `id` as `template_id` on `POST /api/v1/security-groups`. operationId: listSecurityGroupTemplates responses: '200': description: List of templates content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/SecurityGroupTemplate' '401': $ref: '#/components/responses/Unauthorized' /api/v1/security-groups/{id}: get: tags: - Networking summary: Get security group description: Get details of a single security group, including its rules. operationId: getSecurityGroup parameters: - $ref: '#/components/parameters/SecurityGroupResourceIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Security group details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SecurityGroup' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' put: tags: - Networking summary: Update security group description: | Update a security group's name, description, or rules. Passing `rules` replaces the entire rule set — to add or remove a single rule, GET the current rules first, modify the array, then PUT the result. operationId: updateSecurityGroup parameters: - $ref: '#/components/parameters/SecurityGroupResourceIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateSecurityGroupRequest' responses: '200': description: Security group updated content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SecurityGroup' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' delete: tags: - Networking summary: Delete security group description: | Delete a security group. The group must not be attached to any VM NIC. Detach it first via `DELETE /api/v1/vms/{id}/security-groups/{sg_id}/{nic_id}`. operationId: deleteSecurityGroup parameters: - $ref: '#/components/parameters/SecurityGroupResourceIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Security group deleted content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/networks: get: tags: - Networking summary: List VM network interfaces description: | List all network interfaces (NICs) attached to a virtual machine. Each NIC has a `nic_id` used when detaching from a VPC, IP, or security group. Use `?type=` to filter by interface type (`public`, `vpc`, or `ipv6`). operationId: listVMNetworks parameters: - $ref: '#/components/parameters/VMIDPath' - name: type in: query required: false description: Filter to one interface type. Omit to return all. schema: type: string enum: [public, vpc, ipv6] responses: '200': description: List of network interfaces content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/VMNetwork' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/vpc: post: tags: - Networking summary: Attach VM to VPC description: | Attach a virtual machine to a VPC. The VM gains a private network interface inside the VPC. Use `ip` to request a specific private IP from the VPC's CIDR range. If omitted, an IP is auto-assigned. operationId: attachVMToVPC parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AttachVPCRequest' responses: '200': description: VM attached to VPC content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/vpc/{nic_id}: delete: tags: - Networking summary: Detach VM from VPC description: | Detach a VM's network interface from its VPC. Use the `nic_id` from `GET /api/v1/vms/{id}/networks` to identify the interface. operationId: detachVMFromVPC parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/NICIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: VM detached from VPC content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/ip: post: tags: - Networking summary: Attach floating IP to VM description: | Attach a floating public IP to a virtual machine. Pick **one** of two modes: ## Option A — attach a reserved IP (use `ip_id`) Pass `ip_id` set to a previously reserved IP (from `POST /api/v1/ips/reserve` or `GET /api/v1/ips?reserved=true`). The IP must: - belong to the same account - not be currently attached to another VM - have `reserved: true` No new billing — you're already paying for the reserved IP. ## Option B — auto-assign a new IP (use `type`) Leave `ip_id` empty and pass `type` to allocate a fresh IP from the pool: - `type: IPv4` (default if omitted) - `type: IPv6` A new IP is allocated and billed to your account from the moment of attach. Detach to release it back to the pool. ## Notes - VM must not already have a public IP of the same family (detach first). - Optionally pass `security_groups` (UUID array) to apply security groups to the new NIC. operationId: attachVMIP parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AttachIPRequest' responses: '200': description: IP attached content: application/json: schema: $ref: '#/components/schemas/AttachIPResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '403': $ref: '#/components/responses/BillingValidationFailed' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/ip/{nic_id}: delete: tags: - Networking summary: Detach floating IP from VM description: | Detach a floating IP from a VM. The IP returns to your reserved pool (or is released, for auto-assigned IPs). Use the `nic_id` from `GET /api/v1/vms/{id}/networks`. operationId: detachVMIP parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/NICIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: IP detached content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/security-groups: post: tags: - Networking summary: Attach security group to VM NIC description: | Attach a security group to a specific network interface on a VM. The security group's rules apply to inbound and outbound traffic on that NIC. Use `nic_id` from `GET /api/v1/vms/{id}/networks` to target a specific interface. operationId: attachVMSecurityGroup parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AttachSecurityGroupRequest' responses: '200': description: Security group attached content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/security-groups/{sg_id}/{nic_id}: delete: tags: - Networking summary: Detach security group from VM NIC description: Remove a security group from a specific NIC on a VM. operationId: detachVMSecurityGroup parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/SecurityGroupIDPath' - $ref: '#/components/parameters/NICIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Security group detached content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/tags: post: tags: - Virtual Machines summary: Add tag to VM description: | Attach a custom tag to a virtual machine. Tags help organize and filter resources. Returns the updated full tag list for the VM. operationId: addVMTag parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AddVMTagRequest' responses: '200': description: Tag added content: application/json: schema: $ref: '#/components/schemas/VMTagsResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/tags/{tagId}: patch: tags: - Virtual Machines summary: Update VM tag description: | Update a tag's name or priority on a specific VM. Returns the updated full tag list for the VM. ## Finding the tag ID Tags are returned in the `tags` array of `GET /api/v1/vms/{id}`. Each tag includes its `id`, `name`, `priority`, and `created_at`. Use the tag's `id` as `tagId` here. operationId: updateVMTag parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/TagIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateVMTagRequest' responses: '200': description: Tag updated content: application/json: schema: $ref: '#/components/schemas/VMTagsResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' delete: tags: - Virtual Machines summary: Remove VM tag description: | Remove a tag from a VM. Returns the updated full tag list for the VM. ## Finding the tag ID Tags are returned in the `tags` array of `GET /api/v1/vms/{id}`. Each tag includes its `id`, `name`, `priority`, and `created_at`. Use the tag's `id` as `tagId` here. operationId: removeVMTag parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/TagIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Tag removed content: application/json: schema: $ref: '#/components/schemas/VMTagsResponse' '400': $ref: '#/components/responses/MissingProjectID' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/notes: get: tags: - Virtual Machines summary: Get VM notes description: | Get the personal and account-shared notes attached to a VM. - **Personal note** — visible only to the user that wrote it. - **Account note** — visible to all members of the account. Use the optional `type` query param to fetch only one scope. Either or both fields may be absent from the response if no note has been written yet — in that case only `{ "success": true }` is returned. operationId: getVMNotes parameters: - $ref: '#/components/parameters/VMIDPath' - name: type in: query required: false description: | Filter to one note scope. Omit to return both. schema: type: string enum: [personal, account] responses: '200': description: VM notes content: application/json: schema: $ref: '#/components/schemas/VMNotesResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/notes/{type}/append: post: tags: - Virtual Machines summary: Append to VM note description: | Append `content` to the existing note (separated by a newline). If no note exists yet for this scope, the new content becomes the entire note body. Use this when you want to add to a note without losing what's already there — for example, a teammate adding a line to the shared `account` note. operationId: appendVMNote parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/NoteTypePath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpsertVMNoteRequest' responses: '200': description: Content appended (or note created if it didn't exist) content: application/json: schema: type: object properties: success: type: boolean note: $ref: '#/components/schemas/VMNote' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/notes/{type}: patch: tags: - Virtual Machines summary: Update VM note description: | Replace an existing note's content. Unlike `PUT` (which creates the note if missing), `PATCH` returns **404** when no note exists yet for this scope. Use this when you want to fail loudly if the note isn't there — i.e. when you intend to edit, not create. For "create or replace" semantics use `PUT /api/v1/vms/{id}/notes/{type}`. For appending without losing existing content use `POST /api/v1/vms/{id}/notes/{type}/append`. operationId: updateVMNote parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/NoteTypePath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpsertVMNoteRequest' responses: '200': description: Note updated content: application/json: schema: type: object properties: success: type: boolean note: $ref: '#/components/schemas/VMNote' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': description: No note exists yet for this scope. Use `PUT` to create one. content: application/json: schema: $ref: '#/components/schemas/Error' put: tags: - Virtual Machines summary: Upsert VM note description: | Create or fully replace the personal or account note on a VM. ## Behavior This is an **overwrite**, not an append — the request `content` becomes the entire note body. To append, fetch the existing note via `GET /api/v1/vms/{id}/notes` first, concatenate, and PUT the combined value. ## Scopes - **`type: personal`** — one note per user per VM (unique on `vm_id + user_id`). Visible only to the user that wrote it. - **`type: account`** — one note per account per VM (unique on `vm_id + account_id`). Visible to all account members; any member with permission can update it. ## Clearing a note Pass `content: ""` to set an empty note. The note row is kept (still returned by `GET /notes`); only the content is empty. operationId: upsertVMNote parameters: - $ref: '#/components/parameters/VMIDPath' - $ref: '#/components/parameters/NoteTypePath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpsertVMNoteRequest' responses: '200': description: Note saved content: application/json: schema: type: object properties: success: type: boolean note: $ref: '#/components/schemas/VMNote' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/volumes/{id}: patch: tags: - Volumes summary: Resize Volume operationId: resizeVolume description: | Increase the size of a block storage volume. Volume size can only be increased, never decreased. ## Billing For subscription volumes, the storage cost difference is calculated pro-rata and deducted from credits, then account balance. Returns `402` if insufficient balance. parameters: - name: id in: path required: true schema: type: integer description: Volume ID - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ResizeVolumeRequest' responses: '200': description: Volume resized content: application/json: schema: $ref: '#/components/schemas/ResizeResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '404': $ref: '#/components/responses/NotFound' components: securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-Key description: API key for authentication. Each key is bound to a specific account. parameters: VMIDPath: name: id in: path required: true description: VM ID (UUID) schema: type: string format: uuid ProjectIDPath: name: id in: path required: true description: Project ID (UUID) schema: type: string format: uuid ProjectIDHeader: name: X-Project-ID in: header required: true description: Project ID. Required for all mutating operations (create, delete, power actions, resize). schema: type: string format: uuid NICIDPath: name: nic_id in: path required: true description: | Network interface ID. Get it from the `nic_id` field of `GET /api/v1/vms/{id}/networks`. schema: type: integer TagIDPath: name: tagId in: path required: true description: | Tag ID (UUID). Get it from the `tags` array on `GET /api/v1/vms/{id}` — each tag has an `id` field. schema: type: string format: uuid SecurityGroupIDPath: name: sg_id in: path required: true description: | Security group ID (UUID). Get it from the `security_group_id` field of `GET /api/v1/vms/{id}/networks`, or list all account security groups via `GET /api/v1/security-groups` (coming soon). schema: type: string format: uuid SecurityGroupResourceIDPath: name: id in: path required: true description: Security group ID (UUID). schema: type: string format: uuid NoteTypePath: name: type in: path required: true description: Note scope. `personal` is visible only to the writer; `account` is shared across the account. schema: type: string enum: [personal, account] VPCIDPath: name: id in: path required: true description: VPC ID (UUID). schema: type: string format: uuid IPIDPath: name: id in: path required: true description: Floating IP ID (UUID). schema: type: string format: uuid schemas: DeleteVMRequest: type: object properties: volume_action: type: string enum: - detach - delete default: delete description: What to do with attached volumes. `detach` keeps them (still billable), `delete` removes them permanently. delete_vpc: type: boolean default: true description: Whether to delete the associated VPC. Only succeeds if no other VMs are using it. DeleteVMsBulkRequest: type: object required: - ids properties: ids: type: array items: type: string format: uuid minItems: 1 maxItems: 50 description: VM IDs to delete (1–50). volume_action: type: string enum: - detach - delete default: delete description: What to do with attached volumes. `detach` keeps them (still billable), `delete` removes them permanently. delete_vpc: type: boolean default: true description: Whether to delete associated VPCs. Only succeeds if no other VMs are using them. BulkDeleteVMsResult: type: object properties: success: type: boolean description: "`true` if at least one VM was deleted successfully." message: type: string total_count: type: integer description: Number of VMs requested for deletion. success_count: type: integer description: Number of VMs successfully deleted. failed_count: type: integer description: Number of VMs that failed to delete. results: type: array items: $ref: '#/components/schemas/BulkDeleteVMItemResult' description: Per-VM deletion results. BulkDeleteVMItemResult: type: object properties: id: type: string format: uuid description: VM ID. success: type: boolean message: type: string error: type: string description: Error detail if deletion failed. SuccessResponse: type: object properties: success: type: boolean example: true message: type: string example: Operation completed successfully Error: type: object properties: error: type: string message: type: string BillingError: type: object description: Billing validation error response. Returned when the account is not in good standing for paid operations. required: - error - reason properties: error: type: string example: Billing validation failed message: type: string example: Payment failed. Please update your payment method. reason: type: string description: | Machine-readable reason code: - `banned` — account has been suspended - `failed` — last payment failed; balance top-up needed - `no_billing_customer` — billing has not been set up for this account enum: - banned - failed - no_billing_customer VM: type: object required: - id - name - status - cpu - ram - storage - added_storage - total_storage - template_id - template_name - template_version - price_per_hour - pricing_id - region - active - created_at - updated_at properties: id: type: string format: uuid description: Unique VM identifier name: type: string description: VM display name example: my-ubuntu-server status: type: string enum: [active, passive, provisioning, booting, initiating, finalizing, failure] description: | Current VM status. Lifecycle transitions: - Creation: `initiating` → `provisioning` → `booting` → `active` - Stop: `active` → `finalizing` → `passive` - Start: `passive` → `booting` → `active` - Reboot: `active` → `booting` → `active` | Status | Meaning | Billable | |--------|---------|----------| | `initiating` | Queued, initial setup before provisioning | Yes | | `provisioning` | VM being created in the hypervisor | Yes | | `booting` | VM starting up | Yes | | `active` | Running and accessible | Yes | | `passive` | Stopped, resources still reserved | Yes | | `finalizing` | Shutting down | Yes | | `failure` | Creation or operation failed | No | cpu: type: integer description: Number of vCPU cores example: 2 ram: type: integer description: RAM in GB example: 4 storage: type: integer description: Base storage in GB example: 80 added_storage: type: integer description: Additional storage in GB example: 0 total_storage: type: integer description: Total storage (base + added) in GB example: 80 template_id: type: string format: uuid description: OS template ID used to create this VM template_name: type: string description: OS template name example: Ubuntu template_version: type: string description: OS template version example: "24.10x64" version: type: string description: VM version price_per_hour: type: string description: Hourly billing rate in USD example: "0.027764" pricing_id: type: integer description: Pricing plan ID example: 3 created_by: type: string description: User ID who created this VM billing_type: type: string description: Billing type for this VM enum: [payg, subscription] example: payg subscription_id: type: string format: uuid description: Subscription ID if billing_type is subscription backup_type: type: string description: Backup schedule type nullable: true example: weekly region: type: string enum: [us-east] description: Data center region example: us-east project_id: type: string format: uuid description: Project this VM belongs to public_ipv4_address: type: string description: Public IPv4 address example: "15.204.178.3" public_ipv6_address: type: string description: Public IPv6 address nullable: true private_ipv4_address: type: string description: Private IPv4 address (VPC) example: "10.10.0.96" private_ipv6_address: type: string description: Private IPv6 address nullable: true tags: type: array items: $ref: '#/components/schemas/VMTag' description: Custom tags active: type: boolean description: Whether the VM is active created_at: type: string format: date-time updated_at: type: string format: date-time CreateVMRequest: type: object required: - name - template_id - pricing_id - region properties: name: type: string minLength: 1 maxLength: 64 description: VM display name example: my-ubuntu-server template_id: type: string format: uuid description: "OS template ID. Use `GET /api/v1/public/templates` to list available templates." example: "5ac21891-32e6-41ce-8a93-b5d6ab708b0d" pricing_id: type: integer minimum: 1 maximum: 13 description: "Pricing plan ID that determines vCPU, RAM, storage, and bandwidth. Use `GET /api/v1/public/pricing/vm` to list available plans." example: 3 region: type: string enum: [us-east] description: Data center region example: us-east ssh_keys: type: array items: type: string description: SSH public keys for VM access. Required for Linux VMs if no password is provided. Ignored for Windows VMs. password: type: string minLength: 12 description: >- Root password for the VM. Required for Windows VMs. For Linux VMs, required if no SSH keys are provided. Both SSH keys and password can be set on Linux. Must be at least 12 characters with: 2+ uppercase letters, 2+ digits, 1+ special character (@+-_.,!). Only alphanumeric characters and @+-_.,! are allowed. extra_storage: type: integer minimum: 0 description: Additional block storage volume in GB (0–10,000). Attached as a separate disk to the VM. example: 100 extra_storage_type: type: string enum: [ext4, xfs, btrfs] description: Filesystem type for extra storage. Required for Linux VMs with extra storage (defaults to ext4 if omitted). For Windows VMs, storage is automatically formatted as NTFS — this field is ignored. example: ext4 backup_type: type: string enum: [none, daily, weekly] description: "Set `daily` for daily backups or `weekly` for weekly backups. Use `none` or omit for no backups." example: weekly backup_time: type: string description: "Time of day to run backups (e.g. `8am`). Defaults to `8am` if not specified." example: "8am" backup_date: type: string description: "Day of the week for weekly backups (e.g. `Saturday`). Required when `backup_type` is `weekly`, ignored for daily backups. Valid values: Monday–Sunday." example: Saturday tags: type: array items: type: string description: Custom tags for the VM vpc_id: type: string format: uuid description: "Attach VM to an existing VPC by its ID. If omitted along with `vpc_name`, a VPC is auto-created." vpc_name: type: string minLength: 1 maxLength: 64 description: "Create a new VPC with this name. Must be used together with `vpc_cidr`." example: my-custom-vpc vpc_cidr: type: string description: "CIDR block for the new VPC (e.g. `10.0.1.0/24`). Must be used together with `vpc_name`." pattern: "^([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$" example: "10.0.1.0/24" skip_public_ip: type: boolean default: false description: | When `true`, the VM is created without a public IPv4 address. It will only be reachable on its VPC private IP, so a VPC is required — pass `vpc_id` (existing) or `vpc_name` + `vpc_cidr` (new), or leave VPC fields empty to auto-create one. Combining `skip_public_ip=true` with `skip_vpc=true` is rejected because the VM would have no network at all. skip_vpc: type: boolean default: false description: | When `true`, no VPC is created or attached. The VM will only have its public IP — no private networking. Cannot be combined with `skip_public_ip=true`. AddVMTagRequest: type: object required: - name properties: name: type: string minLength: 1 maxLength: 64 description: Tag name. example: production priority: type: integer description: Display priority (lower sorts first). Defaults to 0. example: 0 UpdateVMTagRequest: type: object description: At least one field is required. properties: name: type: string minLength: 1 maxLength: 64 description: New tag name. priority: type: integer description: New display priority. VMTagsResponse: type: object description: Returns the full updated tag list for the VM. required: - success - tags properties: success: type: boolean example: true tags: type: array items: $ref: '#/components/schemas/VMTag' VMNote: type: object description: A free-form note attached to a VM. `personal` notes are visible only to the writer; `account` notes are shared across the account. required: - id - vm_id - user_id - type - content - created_at - updated_at properties: id: type: string format: uuid description: Note identifier. vm_id: type: string format: uuid description: VM this note belongs to. user_id: type: string format: uuid description: User who wrote the note. type: type: string enum: [personal, account] description: Note scope. content: type: string description: Note text. created_at: type: string format: date-time updated_at: type: string format: date-time VMNotesResponse: type: object description: Personal and account notes for a VM. Either or both may be present. required: - success properties: success: type: boolean example: true personal_note: $ref: '#/components/schemas/VMNote' account_note: $ref: '#/components/schemas/VMNote' UpsertVMNoteRequest: type: object required: - content properties: content: type: string description: Note text. Pass an empty string to clear the note. VMNetwork: type: object description: A network interface attached to a VM. required: - nic_id - network_name - ip - type properties: nic_id: type: integer description: Interface ID. Use this when detaching from a VPC, IP, or security group. example: 0 network_name: type: string description: Network name. example: vpc-prod ip: type: string description: IP address assigned to the interface (IPv4 or IPv6 depending on `type`). example: "10.0.1.42" gateway: type: string description: Gateway IP for this interface. example: "10.0.1.1" type: type: string enum: [public, vpc, ipv6] description: | Interface type: - `public` — public IPv4 address - `vpc` — private IPv4 inside a VPC - `ipv6` — public IPv6 address security_group_id: type: string format: uuid description: ID of the security group attached to this NIC, if any. AttachVPCRequest: type: object required: - vpc_id properties: vpc_id: type: string format: uuid description: VPC to attach the VM to. ip: type: string description: Specific private IP within the VPC's CIDR range. Auto-assigned if omitted. example: "10.0.1.42" AttachIPRequest: type: object description: | Provide **either** `ip_id` (attach an existing reserved IP) **or** `type` (auto-allocate a new IP). Don't combine — `ip_id` takes precedence and `type` is ignored when both are sent. properties: ip_id: type: string format: uuid description: | ID of a previously reserved IP. The IP must belong to the same account, be reserved, and not already attached to another VM. No new billing — you're already paying for the reserved IP. type: type: string enum: [IPv4, IPv6] description: | IP family to auto-allocate. Used only when `ip_id` is omitted. Defaults to `IPv4`. A new IP is allocated from the pool and billed to your account from the moment of attach. security_groups: type: array items: type: string format: uuid description: Security group IDs (UUIDs) to apply to the new NIC. AttachIPResponse: type: object required: - success properties: success: type: boolean example: true message: type: string data: type: object properties: vm_id: type: string format: uuid description: VM the IP was attached to. ip_address: type: string description: The attached IP address. nic_id: type: integer description: NIC identifier for this attachment. Use it when detaching. mac: type: string AttachSecurityGroupRequest: type: object required: - security_group_id - nic_id properties: security_group_id: type: string format: uuid description: Security group to attach. nic_id: type: integer description: NIC to attach the security group to. Get from `GET /api/v1/vms/{id}/networks`. SaveImageRequest: type: object required: - name properties: name: type: string minLength: 1 maxLength: 128 description: Custom image name. example: web-server-baseline description: type: string description: Optional description for the saved image. disk_id: type: integer default: 0 description: | Which disk to capture. `0` for the OS disk, `1+` for attached volumes. snapshot_id: type: integer default: -1 description: | Source for the image. `-1` captures the current live disk state. Pass a snapshot ID to capture from a saved snapshot. VMImage: type: object description: A custom OS image saved from a VM disk. Use `id` as `template_id` when creating new VMs. required: - id - name - region properties: id: type: string format: uuid description: Image ID — use as `template_id` when creating a VM. name: type: string description: Image name. version: type: string description: type: string os_type: type: string is_windows: type: boolean region: type: string enum: [us-east] created_at: type: string format: date-time VPC: type: object description: A virtual private cloud (VPC). required: - id - name - cidr - status - region properties: id: type: string format: uuid description: VPC identifier. account_id: type: string format: uuid description: Account this VPC belongs to. project_id: type: string format: uuid description: Project this VPC belongs to. name: type: string description: VPC display name. example: vpc-prod cidr: type: string description: CIDR block for the VPC. example: "10.0.0.0/24" gateway: type: string description: Gateway IP for the VPC. example: "10.0.0.1" dns: type: string description: DNS server IP for the VPC. status: type: string description: VPC lifecycle status. example: active total_ips: type: integer description: Total addressable IPs in the VPC. example: 256 used_ips: type: integer description: IPs currently leased to VMs. example: 4 region: type: string enum: [us-east] description: Region where the VPC lives. gateway_type: type: string description: Gateway type (e.g. `none`, `nat`). router_public_ip: type: string description: Public IP of the VPC gateway, if a gateway is enabled. router_status: type: string description: Gateway router status. created_at: type: string format: date-time updated_at: type: string format: date-time CreateVPCRequest: type: object required: - name - cidr properties: name: type: string minLength: 1 maxLength: 64 description: VPC name. example: vpc-prod cidr: type: string pattern: "^([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$" description: CIDR block. Must not overlap with other VPCs in the account. See `GET /api/v1/vpcs/cidr-suggestions` for non-overlapping options. example: "10.0.0.0/24" region: type: string enum: [us-east] description: Region for the VPC. Defaults to `us-east`. example: us-east UpdateVPCRequest: type: object description: At least one field is required. CIDR cannot be changed after creation. properties: name: type: string minLength: 1 maxLength: 64 description: New VPC name. description: type: string description: VPC description. CIDRSuggestionsResponse: type: object required: - success properties: success: type: boolean suggested: type: string description: Recommended CIDR — non-overlapping and right-sized for typical use. example: "10.1.0.0/24" suggested_ips: type: integer description: Number of usable IPs in the suggested block. example: 256 alternatives: type: array description: Other non-overlapping CIDRs to choose from, sized small/medium/large. items: $ref: '#/components/schemas/CIDRSuggestion' existing_count: type: integer description: Number of VPCs you already have. CIDRSuggestion: type: object properties: cidr: type: string example: "10.2.0.0/24" available_ips: type: integer example: 256 size: type: string enum: [small, medium, large] SecurityGroup: type: object description: A security group — a named set of inbound/outbound rules that can be attached to VM NICs. required: - id - name - rules properties: id: type: string format: uuid description: Security group ID. name: type: string example: web-server description: type: string rules: type: array items: $ref: '#/components/schemas/SecurityGroupRule' vm_count: type: integer description: Number of VM NICs currently using this security group. project_id: type: string format: uuid created_at: type: string format: date-time updated_at: type: string format: date-time SecurityGroupRule: type: object description: A single inbound or outbound rule. required: - protocol - rule_type properties: protocol: type: string enum: [TCP, UDP, ICMP, ICMPV6, IPSEC, ALL] description: Network protocol. rule_type: type: string enum: [INBOUND, OUTBOUND] description: Direction of traffic. range: type: string description: Port or port range. Single port (`80`) or range (`8000:9000`). Empty for ICMP/ALL. example: "80" ip: type: string description: Source/destination IP for the rule. Empty means any. example: "0.0.0.0" size: type: integer description: CIDR block size (e.g. `24` for /24). Used with `ip` to allow a network range. icmp_type: type: integer description: ICMP message type (only for ICMP/ICMPV6 protocols). SecurityGroupTemplate: type: object description: A pre-built security group template. required: - id - name - rules properties: id: type: string description: Template ID — pass as `template_id` when creating a security group. example: web-server name: type: string example: Web Server description: type: string rules: type: array items: $ref: '#/components/schemas/SecurityGroupRule' CreateSecurityGroupRequest: type: object required: - name properties: name: type: string minLength: 1 maxLength: 64 example: web-server description: type: string template_id: type: string description: Seed from a template. Template rules are copied, then merged with any explicit `rules`. example: web-server rules: type: array items: $ref: '#/components/schemas/SecurityGroupRule' UpdateSecurityGroupRequest: type: object description: At least one field is required. Passing `rules` replaces the entire rule set. properties: name: type: string minLength: 1 maxLength: 64 description: type: string rules: type: array items: $ref: '#/components/schemas/SecurityGroupRule' FloatingIP: type: object description: A floating public IP address. required: - id - ip_address - type - status - reserved properties: id: type: string format: uuid description: IP identifier. account_id: type: string format: uuid project_id: type: string format: uuid created_by: type: string format: uuid description: User who reserved the IP. ip_address: type: string description: The IP address. example: "203.0.113.42" type: type: string enum: [IPv4, IPv6] description: IP family. status: type: string enum: [free, in-use] description: | - `free` — IP is in the pool, not currently attached to any VM. - `in-use` — currently attached to a VM. reserved: type: boolean description: | Whether the IP is reserved (held for the account regardless of VM attachment). A reserved IP can be either `free` (held but not attached) or `in-use` (held and attached). region: type: string enum: [us-east] created_at: type: string format: date-time updated_at: type: string format: date-time ReserveIPRequest: type: object description: Optional fields for IP reservation. Send an empty body to use defaults. properties: type: type: string enum: [IPv4, IPv6] default: IPv4 description: IP family to reserve. Defaults to IPv4. region: type: string enum: [us-east] description: Region. Defaults to the project's default region. billing_period: type: string enum: [monthly, yearly, twenty_four_month] default: monthly description: Subscription billing period. Ignored for PAYG accounts. VMTag: type: object description: Custom tag attached to a VM. required: - id - name - priority - created_at properties: id: type: string description: Unique tag identifier name: type: string description: Tag name priority: type: integer description: Tag priority (ordering) created_at: type: string format: date-time description: When the tag was created RenameVMRequest: type: object required: - name properties: name: type: string minLength: 1 maxLength: 64 description: New name for the virtual machine ReinstallVMRequest: type: object required: - template_id properties: template_id: type: string format: uuid description: ID of the OS template to reinstall with password: type: string description: New root/admin password. Either password or auto_generate_password should be provided. ssh_keys: type: array items: type: string description: SSH public keys for authentication auto_generate_password: type: boolean description: Auto-generate a password and email it to the account owner ResizeVMRequest: type: object required: - pricing_id properties: pricing_id: type: integer minimum: 1 description: "Target pricing plan ID. Determines CPU, RAM, and included storage. Use `GET /api/v1/public/pricing/vm` to list available plans." ResizeVMDiskRequest: type: object required: - new_size properties: new_size: type: integer minimum: 1 description: New disk size in GB. Must be larger than current size. ResizeVolumeRequest: type: object required: - new_size properties: new_size: type: integer minimum: 1 description: New volume size in GB. Must be larger than current size. ResizeResponse: type: object properties: success: type: boolean example: true message: type: string id: type: string description: Resource ID. billing: $ref: '#/components/schemas/ResizeBillingDetails' ResizeBillingDetails: type: object description: Present only for subscription-based resources. properties: type: type: string enum: [upgrade, downgrade, none] pro_rata_amount: type: number description: Amount charged (positive) or credited (negative). credits_applied: type: number balance_applied: type: number new_monthly_price: type: number new_price_per_hour: type: string Region: type: object required: - id - code - name - country_code - is_default properties: id: type: integer description: Region identifier example: 1 code: type: string description: Region code used in API requests example: us-east name: type: string description: Human-readable region name example: US East country_code: type: string description: ISO country code example: US flag: type: string description: Country flag emoji example: "\U0001F1FA\U0001F1F8" display_order: type: integer description: Sort order for display example: 1 is_default: type: boolean description: Whether this is the default region example: true Template: type: object required: - id - name - version - cpu - ram - storage - is_windows - os_type - category - region properties: id: type: string format: uuid description: Template ID — use as `template_id` when creating a VM example: "5ac21891-32e6-41ce-8a93-b5d6ab708b0d" name: type: string description: OS name example: Ubuntu version: type: string description: OS version example: "24.10x64" cpu: type: integer description: Minimum vCPU cores required example: 1 ram: type: integer description: Minimum RAM in GB required example: 1 storage: type: integer description: Minimum storage in GB required example: 25 is_windows: type: boolean description: Whether this is a Windows template (only available for premium VMs) example: false os_type: type: string description: Operating system type example: linux category: type: string enum: [os, marketplace] description: Template category example: os description: type: string description: Template description example: "" region: type: string enum: [us-east] description: Region where this template is available example: us-east VMPricingPlan: type: object required: - id - vcpu - memory_gib - ssd_gib - transfer_gib - price_per_hour - monthly_price - yearly_price - twenty_four_month_price - vm_type - region properties: id: type: integer description: Plan ID — use as `pricing_id` when creating a VM example: 3 vcpu: type: integer description: Number of vCPU cores example: 2 memory_gib: type: integer description: RAM in GiB example: 4 ssd_gib: type: integer description: SSD storage in GiB example: 80 transfer_gib: type: integer description: Monthly data transfer in GiB example: 4000 price_per_hour: type: number description: Hourly price in USD (pay-as-you-go) example: 0.027764 monthly_price: type: number description: Monthly price in USD example: 20.00 yearly_price: type: number description: Yearly commitment price in USD (total for 12 months) example: 200.00 twenty_four_month_price: type: number description: 24-month commitment price in USD (total for 24 months) example: 400.00 vm_type: type: string enum: [standard, premium] description: VM type example: standard region: type: string enum: [us-east] description: Region this plan is available in example: us-east StoragePricing: type: object description: Per-GB storage pricing properties: id: type: integer description: Pricing record ID (present for volume pricing) example: 1 price_per_gb_hour: type: number description: Price per GB per hour in USD example: 0.000068 price_per_gb_month: type: number description: Price per GB per month in USD example: 0.05 yearly_price_per_gb: type: number description: Yearly commitment price per GB in USD example: 0.50 twenty_four_month_price_per_gb: type: number description: 24-month commitment price per GB in USD example: 1.00 region: type: string enum: [us-east] description: Region this pricing applies to example: us-east IPPricing: type: object description: IP address pricing grouped by type. properties: ipv4: $ref: '#/components/schemas/IPPricingTier' ipv6: $ref: '#/components/schemas/IPPricingTier' IPPricingTier: type: object description: Pricing for a single IP address type. properties: price_per_hour: type: number description: Hourly price in USD example: 0.005 monthly_price: type: number description: Monthly price in USD example: 3.50 yearly_price: type: number description: Yearly commitment price in USD example: 35.00 twenty_four_month_price: type: number description: 24-month commitment price in USD example: 70.00 Project: type: object required: - id - account_id - name - slug - default_region - is_default - is_active - created_at - updated_at properties: id: type: string format: uuid description: Unique project identifier account_id: type: string format: uuid description: Account this project belongs to name: type: string description: Project name example: production slug: type: string description: URL-friendly project identifier example: production description: type: string description: Project description default_region: type: string enum: [us-east] description: Default region for resources in this project example: us-east is_default: type: boolean description: Whether this is the account's default project is_active: type: boolean description: Whether the project is active created_by: type: string format: uuid description: User who created the project created_at: type: string format: date-time updated_at: type: string format: date-time CreateProjectRequest: type: object required: - name properties: name: type: string minLength: 1 maxLength: 64 description: Project name example: production description: type: string description: Project description default_region: type: string enum: [us-east] description: Default region for resources example: us-east UpdateProjectRequest: type: object properties: name: type: string minLength: 1 maxLength: 64 description: Project name description: type: string description: Project description default_region: type: string enum: [us-east] description: Default region for resources example: us-east responses: BadRequest: description: Invalid request parameters content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Authentication required content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/Error' BillingValidationFailed: description: | Billing validation failed. The account is not in good standing. Check the `reason` field: - `banned` — account suspended - `failed` — last payment failed; top up the account balance - `no_billing_customer` — billing not set up content: application/json: schema: $ref: '#/components/schemas/BillingError' MissingProjectID: description: X-Project-ID header is required for this endpoint content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Bad Request message: X-Project-ID required InsufficientBalance: description: | Account balance is insufficient for this operation. Top up the balance and retry. content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Payment Required message: Insufficient balance for subscription