openapi: 3.0.3 info: title: Raff API description: | REST API for managing cloud infrastructure on Raff. ## 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.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 - name: Snapshots description: Point-in-time copies of VMs and volumes for quick rollback or cloning - name: Backups description: Scheduled and on-demand VM backups with restore capability - name: Backup Schedules description: Recurring daily or weekly backup schedules attached to a VM - name: SSH Keys description: Manage account-level SSH keys for VM provisioning - name: Members description: Account-level members — invite, list, update role, remove - name: Project Members description: Members of a specific project — same model as Members but project-scoped - name: Roles description: Custom roles bundling account or project permissions - name: Permissions description: List the catalog of permission strings used by roles - name: API Keys description: Create and manage API keys for programmatic access - name: Invitations description: Create and cancel email-based invitations to join the account or a project 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. The response wraps the core VPC fields under `data.vpc` and includes additional detail fields (`ip_range_start`, `ip_range_end`, `leases`) at the same level. 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/VPCDetail' '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: get: tags: - Volumes summary: List volumes description: | List volumes for the authenticated account. Filter by VM or region. Pass `X-Project-ID` to scope the list to a single project. When omitted, returns volumes across every project the API key has access to. operationId: listVolumes parameters: - name: X-Project-ID in: header required: false description: Optional project ID to scope the list. Omit to list across all accessible projects. schema: type: string format: uuid - name: vm_id in: query description: Filter by attached VM UUID schema: type: string format: uuid - name: region in: query description: Filter by region schema: type: string enum: [us-east] - name: limit in: query schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of volumes content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/Volume' total: type: integer '401': $ref: '#/components/responses/Unauthorized' post: tags: - Volumes summary: Create volume description: | Create a new block storage volume. Volumes are created asynchronously — the response returns immediately with `pending` status. Optionally attach the volume to an existing VM at create time by passing `vm_id`. Volume and VM must be in the same region. ## Filesystem For Linux VMs, set `filesystem_type` to choose how the volume is formatted: - `ext4` (default), `xfs`, `btrfs` — Linux filesystems - Omit for Windows VMs — they always use NTFS Filesystem is only applied if the volume is attached to a VM that mounts it via cloud-init or the contextualization script. operationId: createVolume parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateVolumeRequest' responses: '202': description: Volume creation accepted content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Volume' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '403': $ref: '#/components/responses/BillingValidationFailed' /api/v1/volumes/{id}: get: tags: - Volumes summary: Get volume description: Get details of a specific volume. operationId: getVolume parameters: - name: id in: path required: true description: Volume ID schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Volume details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Volume' '404': $ref: '#/components/responses/NotFound' delete: tags: - Volumes summary: Delete volume description: | Permanently delete a volume. The volume must be detached from any VM before deletion. ## Billing For subscription volumes, the remaining prepaid balance is refunded pro-rata (hourly precision) to your account balance. operationId: deleteVolume parameters: - name: id in: path required: true description: Volume ID schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Volume deleted content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' 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' /api/v1/volumes/{id}/attach: post: tags: - Volumes summary: Attach volume to VM description: | Attach a volume to a VM. The VM must be in the same region as the volume. A volume can only be attached to one VM at a time — detach it first if it's already attached elsewhere. Inside the guest, the volume appears as `/dev/vdb`, `/dev/vdc`, etc. (virtio-blk). operationId: attachVolume parameters: - name: id in: path required: true description: Volume ID schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AttachVolumeRequest' responses: '200': description: Volume attached content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Volume' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /api/v1/volumes/{id}/detach: post: tags: - Volumes summary: Detach volume from VM description: | Detach a volume from its current VM. Unmount the filesystem inside the guest before calling this endpoint to avoid data corruption. operationId: detachVolume parameters: - name: id in: path required: true description: Volume ID schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Volume detached content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Volume' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /api/v1/snapshots: get: tags: - Snapshots summary: List snapshots description: | List snapshots for the authenticated account. Filter by VM, volume, or type. Pass `X-Project-ID` to scope the list to a single project. When omitted, returns snapshots across every project the API key has access to. operationId: listSnapshots parameters: - name: X-Project-ID in: header required: false description: Optional project ID to scope the list. Omit to list across all accessible projects. schema: type: string format: uuid - name: vm_id in: query description: Filter by VM UUID schema: type: string format: uuid - name: volume_id in: query description: Filter by volume ID schema: type: integer - name: type in: query description: Filter by snapshot type (`vm` or `volume`) schema: type: string enum: [vm, volume] - name: limit in: query schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of snapshots content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/Snapshot' total: type: integer '401': $ref: '#/components/responses/Unauthorized' post: tags: - Snapshots summary: Create snapshot description: | Create a snapshot of a VM or a volume. - For a **VM snapshot**, set `resource_type` to `vm` and pass the VM UUID as `resource_id`. - For a **volume snapshot**, set `resource_type` to `volume` and pass the integer `volume_id`. operationId: createSnapshot parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateSnapshotRequest' responses: '201': description: Snapshot created content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Snapshot' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '403': $ref: '#/components/responses/BillingValidationFailed' /api/v1/snapshots/{id}: get: tags: - Snapshots summary: Get snapshot description: Get details of a specific snapshot. operationId: getSnapshot parameters: - name: id in: path required: true description: Snapshot ID schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Snapshot details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Snapshot' '404': $ref: '#/components/responses/NotFound' delete: tags: - Snapshots summary: Delete snapshot description: Permanently delete a snapshot. operationId: deleteSnapshot parameters: - name: id in: path required: true description: Snapshot ID schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Snapshot deleted content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' /api/v1/snapshots/{id}/restore: post: tags: - Snapshots summary: Restore snapshot description: | Revert the source VM (or volume) to the state captured in this snapshot. For VM snapshots, the VM must be in `passive` state (powered off) before calling this endpoint. After a successful restore, this snapshot's `status` flips to `active` and the previously-active snapshot (if any) is cleared. An `active` snapshot cannot be deleted — restore the source to a different snapshot first, or take a fresh one. operationId: restoreSnapshot parameters: - name: id in: path required: true description: Snapshot ID schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Snapshot restore initiated content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /api/v1/snapshots/{id}/rename: patch: tags: - Snapshots summary: Rename snapshot description: Rename a snapshot. Updates the display name only — does not affect the underlying disk image. operationId: renameSnapshot parameters: - name: id in: path required: true description: Snapshot ID schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RenameSnapshotRequest' responses: '200': description: Snapshot renamed content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Snapshot' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /api/v1/backups: get: tags: - Backups summary: List backups description: | List backups for the authenticated account. Filter by VM. Pass `X-Project-ID` to scope the list to a single project. When omitted, returns backups across every project the API key has access to. operationId: listBackups parameters: - name: X-Project-ID in: header required: false description: Optional project ID to scope the list. Omit to list across all accessible projects. schema: type: string format: uuid - name: vm_id in: query description: Filter by VM UUID schema: type: string format: uuid - name: limit in: query schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of backups content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/Backup' total: type: integer '401': $ref: '#/components/responses/Unauthorized' post: tags: - Backups summary: Create backup description: | Create an on-demand backup of a VM. The backup is created asynchronously — the response returns immediately with a `pending` status. Use [List Backups](#tag/Backups/operation/listBackups) or [Get Backup](#tag/Backups/operation/getBackup) to track completion. operationId: createBackup parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateBackupRequest' responses: '202': description: Backup creation accepted content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Backup' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '403': $ref: '#/components/responses/BillingValidationFailed' /api/v1/backups/{id}: get: tags: - Backups summary: Get backup description: Get details of a specific backup. operationId: getBackup parameters: - name: id in: path required: true description: Backup ID (UUID) schema: type: string format: uuid - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Backup details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Backup' '404': $ref: '#/components/responses/NotFound' delete: tags: - Backups summary: Delete backup description: Permanently delete a backup. operationId: deleteBackup parameters: - name: id in: path required: true description: Backup ID (UUID) schema: type: string format: uuid - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Backup deleted content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' /api/v1/backups/{id}/restore: post: tags: - Backups summary: Restore backup description: | Restore a VM from a backup. The restore runs asynchronously — the response returns immediately with a `pending` status. The target VM is overwritten in place. The VM should be powered off before restoring. operationId: restoreBackup parameters: - name: id in: path required: true description: Backup ID (UUID) schema: type: string format: uuid - $ref: '#/components/parameters/ProjectIDHeader' responses: '202': description: Backup restore accepted content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Backup' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /api/v1/backups/{id}/chain: delete: tags: - Backups summary: Delete backup series description: | Delete an entire backup series — every restore point that the given backup belongs to. The series is identified by passing the UUID of **any** backup row in it; all sibling restore points (and the underlying storage) are removed in one call. Use this when a single restore point can't be removed on its own because it has older restore points it depends on. The single-backup [Delete backup](/api-reference/virtual-machines/delete-backup) endpoint returns a clear error in that case and points here. operationId: deleteBackupChain parameters: - name: id in: path required: true description: UUID of any backup row in the series to remove. schema: type: string format: uuid - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Backup series deleted content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' /api/v1/vms/{id}/backups/reset-chain: post: tags: - Backups summary: Reset backup series description: | Start a fresh backup series for the VM. The current series is closed — existing restore points remain restorable until you delete them — and the next backup creates a new independent baseline. Use when a series has grown long and you want a clean baseline for future backups without losing the restore points you already have. operationId: resetBackupChain parameters: - name: id in: path required: true description: VM UUID schema: type: string format: uuid - $ref: '#/components/parameters/ProjectIDHeader' responses: '202': description: New baseline queued — the next backup will start a fresh series. content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /api/v1/backup-schedules: get: tags: - Backup Schedules summary: List backup schedules description: | List backup schedules for the authenticated account. Filter by VM. Pass `X-Project-ID` to scope the list to a single project. When omitted, returns schedules across every project the API key has access to. operationId: listBackupSchedules parameters: - name: X-Project-ID in: header required: false description: Optional project ID to scope the list. Omit to list across all accessible projects. schema: type: string format: uuid - name: vm_id in: query description: Filter by VM UUID schema: type: string format: uuid - name: limit in: query schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of backup schedules content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/BackupSchedule' total: type: integer '401': $ref: '#/components/responses/Unauthorized' post: tags: - Backup Schedules summary: Create backup schedule description: | Create a recurring backup schedule for a VM. - **Daily** — set `type` to `daily`. Runs every day at `time`. - **Weekly** — set `type` to `weekly` and pass `day_of_week` (Monday–Sunday). Runs on that day at `time`. `keep_count` controls retention — older backups beyond this count are pruned automatically. operationId: createBackupSchedule parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateBackupScheduleRequest' responses: '201': description: Backup schedule created content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/BackupSchedule' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/backup-schedules/{id}: get: tags: - Backup Schedules summary: Get backup schedule description: Get details of a specific backup schedule. operationId: getBackupSchedule parameters: - name: id in: path required: true description: Backup schedule ID — the `id` field returned by [List Backup Schedules](#tag/Backup-Schedules/operation/listBackupSchedules) or [Create Backup Schedule](#tag/Backup-Schedules/operation/createBackupSchedule). schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Backup schedule details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/BackupSchedule' '404': $ref: '#/components/responses/NotFound' patch: tags: - Backup Schedules summary: Update backup schedule description: Update one or more fields on an existing backup schedule. Omitted fields are unchanged. operationId: updateBackupSchedule parameters: - name: id in: path required: true description: Backup schedule ID — the `id` field returned by [List Backup Schedules](#tag/Backup-Schedules/operation/listBackupSchedules) or [Create Backup Schedule](#tag/Backup-Schedules/operation/createBackupSchedule). schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateBackupScheduleRequest' responses: '200': description: Backup schedule updated content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/BackupSchedule' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' delete: tags: - Backup Schedules summary: Delete backup schedule description: Delete a backup schedule. Existing backups created by this schedule are retained. operationId: deleteBackupSchedule parameters: - name: id in: path required: true description: Backup schedule ID — the `id` field returned by [List Backup Schedules](#tag/Backup-Schedules/operation/listBackupSchedules) or [Create Backup Schedule](#tag/Backup-Schedules/operation/createBackupSchedule). schema: type: integer - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Backup schedule deleted content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' /api/v1/ssh-keys: get: tags: - SSH Keys summary: List SSH keys description: List SSH keys for the authenticated account. SSH keys are account-level — no project scoping. operationId: listSSHKeys responses: '200': description: List of SSH keys content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/SSHKey' '401': $ref: '#/components/responses/Unauthorized' post: tags: - SSH Keys summary: Create SSH key description: | Add an SSH public key to your account. Pass the key on VM create via the `ssh_keys` array to enable key-based login. Supported key types: RSA, ECDSA, Ed25519. The `key_type` field on the response is parsed automatically from the public key. operationId: createSSHKey requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateSSHKeyRequest' responses: '201': description: SSH key created content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SSHKey' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/ssh-keys/{id}: get: tags: - SSH Keys summary: Get SSH key description: Get details of a specific SSH key. operationId: getSSHKey parameters: - name: id in: path required: true description: SSH key ID (UUID) schema: type: string format: uuid responses: '200': description: SSH key details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SSHKey' '404': $ref: '#/components/responses/NotFound' put: tags: - SSH Keys summary: Update SSH key description: Update an SSH key's display name. The public key itself cannot be changed — delete and recreate to rotate. operationId: updateSSHKey parameters: - name: id in: path required: true description: SSH key ID (UUID) schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateSSHKeyRequest' responses: '200': description: SSH key updated content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SSHKey' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' delete: tags: - SSH Keys summary: Delete SSH key description: Permanently delete an SSH key from your account. VMs that already have this key installed continue to accept it until the key is removed from the VM's `~/.ssh/authorized_keys`. operationId: deleteSSHKey parameters: - name: id in: path required: true description: SSH key ID (UUID) schema: type: string format: uuid responses: '200': description: SSH key deleted content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SSHKey' '404': $ref: '#/components/responses/NotFound' /api/v1/members: get: tags: - Members summary: List account members description: List all members of the authenticated account, including pending invitations. operationId: listMembers parameters: - name: status in: query description: Filter by member status schema: type: string enum: [pending, active, suspended] - name: limit in: query schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of account members content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/Member' total: type: integer '401': $ref: '#/components/responses/Unauthorized' post: tags: - Members summary: Add account member description: | Add a new member to the account. There are three ways: - **Email invitation** — pass `email` only. An invitation is sent; the recipient accepts it from the dashboard. The member appears in `pending` status until accepted. Equivalent to calling `POST /api/v1/invitations` directly. - **Direct add** — pass `target_user_id` for a user who already has an account on Raff (e.g. a project-only user being promoted to account member). - **API key** — pass `api_key_id` to make an existing API key a member with the given role. Mutually exclusive with `email` and `target_user_id`. operationId: addMember requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AddMemberRequest' responses: '201': description: Member added (or invitation sent) content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Member' '400': $ref: '#/components/responses/BadRequest' /api/v1/members/{id}: get: tags: - Members summary: Get account member description: Get details of a specific account member. operationId: getMember parameters: - name: id in: path required: true description: Member ID (UUID) schema: type: string format: uuid responses: '200': description: Member details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Member' '404': $ref: '#/components/responses/NotFound' patch: tags: - Members summary: Update account member description: Update a member's role or status. Pass only the fields you want to change. operationId: updateMember parameters: - name: id in: path required: true description: Member ID (UUID) schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateMemberRequest' responses: '200': description: Member updated content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Member' '404': $ref: '#/components/responses/NotFound' delete: tags: - Members summary: Remove account member description: Remove a member from the account. Account owners cannot be removed via this endpoint — transfer ownership first from the dashboard. operationId: removeMember parameters: - name: id in: path required: true description: Member ID (UUID) schema: type: string format: uuid responses: '200': description: Member removed content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' /api/v1/projects/{id}/members: get: tags: - Project Members summary: List project members description: List members of a specific project. operationId: listProjectMembers parameters: - $ref: '#/components/parameters/ProjectIDPath' - name: status in: query description: Filter by member status schema: type: string enum: [pending, active, suspended] - name: limit in: query schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of project members content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/ProjectMember' total: type: integer '404': $ref: '#/components/responses/NotFound' post: tags: - Project Members summary: Add project member description: | Add an existing account member (or API key) to a project with a specific role. - Pass `target_user_id` to add a user. The user must already be a member of the account. - Pass `api_key_id` to grant an API key access to this project. Mutually exclusive with `target_user_id`. To invite a brand-new email to a project, use [Create Project Invitation](#tag/Invitations/operation/createProjectInvitation) instead. operationId: addProjectMember parameters: - $ref: '#/components/parameters/ProjectIDPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AddProjectMemberRequest' responses: '201': description: Project member added content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/ProjectMember' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /api/v1/projects/{id}/members/{member_id}: get: tags: - Project Members summary: Get project member operationId: getProjectMember parameters: - $ref: '#/components/parameters/ProjectIDPath' - name: member_id in: path required: true description: Project member ID (UUID) schema: type: string format: uuid responses: '200': description: Project member details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/ProjectMember' '404': $ref: '#/components/responses/NotFound' patch: tags: - Project Members summary: Update project member description: Change a project member's role or status. operationId: updateProjectMember parameters: - $ref: '#/components/parameters/ProjectIDPath' - name: member_id in: path required: true description: Project member ID (UUID) schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateMemberRequest' responses: '200': description: Project member updated content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/ProjectMember' '404': $ref: '#/components/responses/NotFound' delete: tags: - Project Members summary: Remove project member description: Remove a member from a project. The user remains an account member; only the project-scoped grant is revoked. operationId: removeProjectMember parameters: - $ref: '#/components/parameters/ProjectIDPath' - name: member_id in: path required: true description: Project member ID (UUID) schema: type: string format: uuid responses: '200': description: Project member removed content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' /api/v1/roles: get: tags: - Roles summary: List roles description: | List all roles available to the account — both **system roles** (e.g. `Owner`, `Admin`, `Member`, `Operator`) and any **custom roles** the account has defined. operationId: listRoles parameters: - name: scope in: query description: Filter by scope (`account` or `project`) schema: type: string enum: [account, project] - name: limit in: query schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of roles content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/Role' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Roles summary: Create custom role description: | Create a custom role bundling a set of permissions at either account or project scope. Get the list of valid permission strings from [List Permissions](#tag/Permissions/operation/listPermissions). operationId: createRole requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateRoleRequest' responses: '201': description: Role created content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Role' '400': $ref: '#/components/responses/BadRequest' /api/v1/roles/{id}: get: tags: - Roles summary: Get role operationId: getRole parameters: - name: id in: path required: true description: Role ID (UUID) schema: type: string format: uuid responses: '200': description: Role details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Role' '404': $ref: '#/components/responses/NotFound' patch: tags: - Roles summary: Update custom role description: | Update a custom role's name, description, or permission set. **System roles cannot be edited** — return `400 Bad Request`. Editing a role applies to every member and API key currently using it. operationId: updateRole parameters: - name: id in: path required: true description: Role ID (UUID) schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateRoleRequest' responses: '200': description: Role updated content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Role' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' delete: tags: - Roles summary: Delete custom role description: | Delete a custom role. The role must not be in use by any member or API key — reassign them first or deletion will fail. System roles cannot be deleted. operationId: deleteRole parameters: - name: id in: path required: true description: Role ID (UUID) schema: type: string format: uuid responses: '200': description: Role deleted content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /api/v1/permissions: get: tags: - Permissions summary: List permissions description: | List the full catalog of permission strings recognized by the role system. Use this to discover valid values for the `permissions` array on [Create Role](#tag/Roles/operation/createRole) and [Update Role](#tag/Roles/operation/updateRole). Requires authentication, but no specific permission — any valid API key can read the catalog. The catalog is the same for every account. operationId: listPermissions parameters: - name: scope in: query description: Filter by scope schema: type: string enum: [account, project] responses: '200': description: List of permissions content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/Permission' /api/v1/api-keys: get: tags: - API Keys summary: List API keys description: | List API keys belonging to the account. Only key prefixes are returned — full secrets are only shown once at create time. operationId: listApiKeys parameters: - name: scope in: query description: Filter by key scope. Customer-created keys are always `public`, so this is rarely useful — included for completeness. schema: type: string enum: [public] - name: limit in: query schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of API keys content: application/json: schema: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/APIKey' total: type: integer '401': $ref: '#/components/responses/Unauthorized' post: tags: - API Keys summary: Create API key description: | Create a new API key. The full secret is returned **once** in the `secret` field of the response — store it immediately. Subsequent reads only return the `key_prefix`. Pass `role_id` to bind the key to an account role; the key inherits that role's permissions. To grant per-project access, after creating the key add it as a [Project Member](#tag/Project-Members/operation/addProjectMember) on each project (passing `api_key_id`). operationId: createApiKey requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateAPIKeyRequest' responses: '201': description: API key created (full secret returned once) content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/APIKeyWithSecret' '400': $ref: '#/components/responses/BadRequest' /api/v1/api-keys/{id}: get: tags: - API Keys summary: Get API key description: Get an API key's metadata. The secret is never returned — only the prefix. operationId: getApiKey parameters: - name: id in: path required: true description: API key ID (UUID) schema: type: string format: uuid responses: '200': description: API key details content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/APIKey' '404': $ref: '#/components/responses/NotFound' patch: tags: - API Keys summary: Update API key description: | Update an API key's name, expiry, rate-limit tier, or active flag. Permissions cannot be changed via this endpoint — change the role assigned to the key instead, or rotate to a new key. operationId: updateApiKey parameters: - name: id in: path required: true description: API key ID (UUID) schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateAPIKeyRequest' responses: '200': description: API key updated content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/APIKey' '404': $ref: '#/components/responses/NotFound' delete: tags: - API Keys summary: Revoke API key description: Permanently revoke an API key. In-flight requests using this key fail with `401`. Cannot be undone. operationId: revokeApiKey parameters: - name: id in: path required: true description: API key ID (UUID) schema: type: string format: uuid responses: '200': description: API key revoked content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' /api/v1/api-keys/{id}/regenerate: post: tags: - API Keys summary: Regenerate API key description: | Rotate an API key — issues a new secret value and immediately invalidates the previous secret. The key keeps its name, scope, role, and expiration. The full new secret is returned **once** in `secret`. Update your secret store immediately. operationId: regenerateApiKey parameters: - name: id in: path required: true description: API key ID (UUID) schema: type: string format: uuid responses: '200': description: API key regenerated (full secret returned once) content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/APIKeyWithSecret' '404': $ref: '#/components/responses/NotFound' /api/v1/invitations: post: tags: - Invitations summary: Create account invitation description: | Send an email invitation for someone to join the account with the given role. The recipient gets an email with an accept link. Until they accept, the invitation appears in [List Members](#tag/Members/operation/listMembers) with `status: pending`. operationId: createAccountInvitation requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateInvitationRequest' responses: '201': description: Invitation sent content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Invitation' '400': $ref: '#/components/responses/BadRequest' /api/v1/invitations/{id}: delete: tags: - Invitations summary: Cancel invitation description: Cancel a pending invitation. The accept link in the recipient's email becomes invalid. operationId: cancelInvitation parameters: - name: id in: path required: true description: Invitation ID (UUID) schema: type: string format: uuid responses: '200': description: Invitation cancelled content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' /api/v1/projects/{id}/invitations: post: tags: - Invitations summary: Create project invitation description: | Send an email invitation for someone to join a specific project with the given role. The recipient gets an email with an accept link. operationId: createProjectInvitation parameters: - $ref: '#/components/parameters/ProjectIDPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateInvitationRequest' responses: '201': description: Invitation sent content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/Invitation' '400': $ref: '#/components/responses/BadRequest' '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`. 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" mac: type: string description: MAC address of the interface. Stable for the lifetime of the VM — useful for static DHCP, packet captures, and disambiguating multiple NICs in scripts. example: "02:00:5e:00:53:0a" 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 VPCDetail: type: object description: | Detail wrapper returned by `GET /api/v1/vpcs/{id}`. Contains the core VPC under `vpc`, plus the configured IP range and the current set of IP leases (one entry per attached network interface). required: - vpc properties: vpc: $ref: '#/components/schemas/VPC' ip_range_start: type: string description: First IP in the VPC's allocatable range example: 10.0.1.10 ip_range_end: type: string description: Last IP in the VPC's allocatable range example: 10.0.1.254 leases: type: array description: Active IP leases on the VPC. Empty when no VMs are attached. items: $ref: '#/components/schemas/VPCLease' VPCLease: type: object description: A single IP lease on the VPC. required: - ip - nic_id properties: ip: type: string description: Leased IP address example: 10.0.1.42 nic_id: type: integer description: NIC interface ID on the leased VM. Pair with `GET /api/v1/vms/{id}/networks` to identify the owning interface. 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. Volume: type: object required: - id - name - volume_type - size - status properties: id: type: integer description: Volume ID. Use this in `/volumes/{id}` for get, delete, attach, detach, resize. account_id: type: string format: uuid description: Account that owns the volume project_id: type: string format: uuid description: Project the volume belongs to product_vm: type: string format: uuid description: UUID of the VM the volume is currently attached to. Empty when detached. name: type: string description: Volume display name example: data-vol-1 volume_type: type: string description: Storage class enum: [nvme] example: nvme size: type: integer description: Volume size in GB example: 100 status: type: string enum: [creating, available, attached, deleting, failed] description: | Volume lifecycle status: - `creating` — being provisioned on the hypervisor - `available` — ready, not attached to any VM - `attached` — attached to a VM (see `product_vm`) - `deleting` — being torn down - `failed` — creation or attach failed price_per_hour: type: string description: Hourly billing rate in USD region: type: string enum: [us-east] created_at: type: string format: date-time updated_at: type: string format: date-time CreateVolumeRequest: type: object required: - name - size - volume_type properties: name: type: string minLength: 1 maxLength: 64 description: Volume display name example: data-vol-1 size: type: integer minimum: 1 description: Volume size in GB example: 100 volume_type: type: string enum: [nvme] description: Storage class example: nvme filesystem_type: type: string enum: [ext4, xfs, btrfs] description: Filesystem to format the volume with on first attach. Linux only — Windows VMs use NTFS automatically. Defaults to `ext4`. vm_id: type: string format: uuid description: Optional. Attach the volume to this VM at create time. VM must be in the same region. region: type: string enum: [us-east] description: Region. Defaults to the project's default region. AttachVolumeRequest: type: object required: - vm_id properties: vm_id: type: string format: uuid description: UUID of the VM to attach this volume to. VM must be in the same region as the volume. 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 out_of_stock: type: boolean description: Whether this plan is currently sold out. Informational only — the plan is still listed and can be ordered. example: false 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 Snapshot: type: object required: - id - type - name properties: id: type: integer description: Snapshot ID account_id: type: string format: uuid description: Account that owns the snapshot project_id: type: string format: uuid description: Project the snapshot belongs to created_by: type: string description: User ID that created the snapshot type: type: string enum: [vm, volume] description: Snapshot source type name: type: string description: Snapshot display name size: type: string description: Current snapshot size target_size: type: string description: Provisioned size of the source disk at snapshot time status: type: string description: | Indicates whether the source VM or volume is currently running on this snapshot: - `active` — this snapshot is **in use**. The source VM/volume has been reverted to it and is running on it now. Deleting an active snapshot is blocked. - `""` (empty) — this snapshot is saved but **not in use**. The source has not been reverted to it (or has been reverted to a different snapshot since). Only one snapshot per source can be `active` at a time. Calling [Restore Snapshot](#tag/Snapshots/operation/restoreSnapshot) flips that snapshot to `active` and clears the previous active one. enum: ["", active] example: active product_vm: type: string format: uuid description: Source VM UUID (when `type` is `vm`) price_per_hour: type: string description: Hourly storage cost in USD created_at: type: string format: date-time CreateSnapshotRequest: type: object required: - resource_type - name properties: resource_type: type: string enum: [vm, volume] description: Source type for the snapshot resource_id: type: string format: uuid description: Source VM UUID. Required when `resource_type` is `vm`. volume_id: type: integer description: Source volume ID. Required when `resource_type` is `volume`. name: type: string minLength: 1 maxLength: 128 description: Snapshot display name example: pre-upgrade RenameSnapshotRequest: type: object required: - name properties: name: type: string minLength: 1 maxLength: 128 description: New snapshot display name Backup: type: object required: - id - name - storage_size - status properties: id: type: string format: uuid description: Backup ID account_id: type: string format: uuid description: Account that owns the backup project_id: type: string format: uuid description: Project the backup belongs to created_by: type: string description: User ID that created the backup region: type: string enum: [us-east] description: Region the backup is stored in product_vm: type: string format: uuid description: Source VM UUID name: type: string description: Backup display name storage_size: type: integer description: | Backup size in **MB**. For incremental restore points this is the per-increment delta; for legacy standalone backups it is the full image size. Display in MB up to 1024, else convert to GB (`storage_size / 1024`). Pricing math: `(storage_size / 1024) * price_per_gb`. status: type: string description: | Backup lifecycle status. Common values: - `pending` — creation queued - `creating` — backup being captured - `ready` — available for restore - `restoring` — currently restoring to a VM - `failed` — creation or restore failed price_per_hour: type: string description: Hourly storage cost in USD expire_date: type: string format: date-time description: When the backup will be auto-pruned (only set when retention applies) increment_id: type: integer nullable: true description: | Restore-point position within an incremental backup series. - `null` — legacy standalone backup (no series). - `0` — first restore point of a series. - `1`, `2`, … — subsequent restore points in the same series. Backups that share the same series are deleted together. Deleting a single restore point that has older points in its series triggers a series-wide delete. Use [Delete backup series](/api-reference/virtual-machines/delete-backup-series) to remove a whole series explicitly, or [Reset backup series](/api-reference/virtual-machines/reset-backup-series) to start a fresh baseline on the next backup. created_at: type: string format: date-time CreateBackupRequest: type: object required: - vm_id properties: vm_id: type: string format: uuid description: Source VM UUID name: type: string maxLength: 128 description: Optional backup display name. Defaults to a timestamped name. BackupSchedule: type: object required: - id - name - type - keep_count properties: id: type: integer description: Backup schedule ID. Use this value in `/backup-schedules/{id}` for get, update, and delete. account_id: type: string format: uuid project_id: type: string format: uuid created_by: type: string region: type: string enum: [us-east] product_vm: type: string format: uuid description: Source VM UUID name: type: string description: Schedule display name type: type: string enum: [daily, weekly] description: Schedule frequency runtime: type: string description: Human-readable schedule (e.g. `Daily at 8am`, `Saturday 8am`) keep_count: type: integer description: Number of backups retained before auto-pruning price_per_hour: type: string description: Hourly cost in USD size: type: string description: Total size of backups currently retained for this schedule created_at: type: string format: date-time CreateBackupScheduleRequest: type: object required: - vm_id - type - time - keep_count properties: vm_id: type: string format: uuid description: Target VM UUID type: type: string enum: [daily, weekly] description: Schedule frequency time: type: string description: Time of day to run the backup (e.g. `8am`, `13:00`) example: 8am day_of_week: type: string enum: [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday] description: Day of week. Required when `type` is `weekly`. keep_count: type: integer minimum: 1 description: Number of backups to retain before auto-pruning UpdateBackupScheduleRequest: type: object properties: type: type: string enum: [daily, weekly] time: type: string example: 8am day_of_week: type: string enum: [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday] description: Required when `type` is `weekly`. keep_count: type: integer minimum: 1 SSHKey: type: object required: - id - name - public_key - key_type properties: id: type: string format: uuid description: SSH key ID name: type: string description: SSH key display name example: laptop public_key: type: string description: Full SSH public key string (e.g. `ssh-ed25519 AAAA... user@host`) key_type: type: string description: Parsed key algorithm enum: [ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521] created_at: type: string format: date-time updated_at: type: string format: date-time CreateSSHKeyRequest: type: object required: - name - public_key properties: name: type: string minLength: 1 maxLength: 128 description: SSH key display name example: laptop public_key: type: string description: Full SSH public key string example: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... user@host" UpdateSSHKeyRequest: type: object required: - name properties: name: type: string minLength: 1 maxLength: 128 description: New SSH key display name Member: type: object required: - id - email - status properties: id: type: string format: uuid description: Account member ID account_id: type: string format: uuid user_id: type: string format: uuid nullable: true description: User UUID. `null` for pending invitations until the invitee accepts. email: type: string format: email role: type: string description: Role slug (deprecated — prefer `role_id` and `role_name`) role_id: type: string format: uuid role_name: type: string example: Operator status: type: string enum: [pending, active, suspended] description: | Membership status: - `pending` — invited but not yet accepted - `active` — full member - `suspended` — temporarily disabled by an admin invited_by: type: string format: uuid invited_at: type: string format: date-time accepted_at: type: string format: date-time invitation_id: type: string format: uuid description: Set when `status` is `pending` — the underlying invitation ID. Use [Cancel Invitation](#tag/Invitations/operation/cancelInvitation) to revoke. invitation_expires_at: type: string format: date-time created_at: type: string format: date-time updated_at: type: string format: date-time AddMemberRequest: type: object properties: email: type: string format: email description: Email to invite. Mutually exclusive with `target_user_id` and `api_key_id`. target_user_id: type: string format: uuid description: User UUID to add directly. The user must already exist on Raff (e.g. an existing project-only member). Mutually exclusive with `email` and `api_key_id`. api_key_id: type: string format: uuid description: API key UUID to grant account-level access via this membership. Mutually exclusive with `email` and `target_user_id`. role_id: type: string format: uuid description: Role UUID. Required. UpdateMemberRequest: type: object description: At least one field is required. properties: role_id: type: string format: uuid status: type: string enum: [active, suspended] description: Set `active` to re-enable a suspended member, or `suspended` to disable. `pending` cannot be set directly — that's reserved for invitation flow. ProjectMember: type: object required: - id - email - status properties: id: type: string format: uuid description: Project member ID project_id: type: string format: uuid user_id: type: string format: uuid nullable: true email: type: string format: email role: type: string description: Role slug (deprecated — prefer `role_id` and `role_name`) role_id: type: string format: uuid role_name: type: string status: type: string enum: [pending, active, suspended] description: | Membership status: - `pending` — invited but not yet accepted - `active` — full member - `suspended` — temporarily disabled by an admin invited_by: type: string format: uuid invited_at: type: string format: date-time accepted_at: type: string format: date-time invitation_id: type: string format: uuid invitation_expires_at: type: string format: date-time created_at: type: string format: date-time AddProjectMemberRequest: type: object required: - role_id properties: target_user_id: type: string format: uuid description: User UUID to add. Must already be an account member. Mutually exclusive with `api_key_id`. api_key_id: type: string format: uuid description: API key UUID to grant access to this project. Mutually exclusive with `target_user_id`. role_id: type: string format: uuid Role: type: object required: - id - name - slug - scope - permissions - is_system properties: id: type: string format: uuid account_id: type: string format: uuid description: Empty for built-in system roles name: type: string example: Operator slug: type: string example: operator description: type: string scope: type: string enum: [account, project] description: Whether the role grants account-level or project-level permissions permissions: type: array items: type: string description: Permission strings (e.g. `vm.create`, `project.members.view`). See [List Permissions](#tag/Permissions/operation/listPermissions). is_system: type: boolean description: '`true` for built-in roles (Owner, Admin, Member, Operator, etc.) which cannot be edited or deleted. `false` for custom roles.' created_by: type: string format: uuid created_at: type: string format: date-time updated_at: type: string format: date-time CreateRoleRequest: type: object required: - name - slug - scope - permissions properties: name: type: string minLength: 1 maxLength: 64 example: Custom Operator slug: type: string pattern: '^[a-z0-9-]+$' maxLength: 64 example: custom-operator description: type: string scope: type: string enum: [account, project] permissions: type: array items: type: string minItems: 1 example: [vm.view, vm.create, volume.view] UpdateRoleRequest: type: object description: At least one field is required. Slug and scope cannot be changed after creation. properties: name: type: string minLength: 1 maxLength: 64 description: type: string permissions: type: array items: type: string Permission: type: object required: - name - scope - category properties: name: type: string example: vm.create description: type: string example: Create new virtual machines scope: type: string enum: [account, project] category: type: string example: vm APIKeyProjectAccess: type: object properties: project_id: type: string format: uuid project_name: type: string role_id: type: string format: uuid role_name: type: string role_slug: type: string APIKey: type: object required: - id - name - key_prefix - is_active properties: id: type: string format: uuid account_id: type: string format: uuid name: type: string key_prefix: type: string description: First 13 characters of the key — the `raff_` prefix plus the first 8 hex characters (e.g. `raff_17d70fcf`). The full secret is only returned once at create or regenerate time. example: raff_17d70fcf scope: type: string enum: [public] description: Always `public` for customer-created keys rate_limit_tier: type: string enum: [standard, high] description: Rate-limit tier. Standard is 30 RPS / burst 60; High is 100 RPS / burst 200. expires_at: type: string format: date-time description: When the key auto-expires. Omitted for never-expires keys. last_used_at: type: string format: date-time is_active: type: boolean role_id: type: string format: uuid description: Account-level role this key uses role_name: type: string role_slug: type: string project_accesses: type: array items: $ref: '#/components/schemas/APIKeyProjectAccess' description: Per-project access grants created_by: type: string format: uuid created_at: type: string format: date-time APIKeyWithSecret: allOf: - $ref: '#/components/schemas/APIKey' - type: object required: - secret properties: secret: type: string description: | The full API key. **Returned only once** — at create or regenerate time. Store immediately; the secret cannot be retrieved later. example: raff_17d70fcf7e7510968a4c19279b25707f088f1cc5ad8b74210341d8e470b5bb7a CreateAPIKeyRequest: type: object required: - name properties: name: type: string minLength: 1 maxLength: 128 description: A descriptive label for the key (e.g. `Production CI`, `Backups Lambda`) rate_limit_tier: type: string enum: [standard, high] default: standard description: '`standard` (30 RPS) by default. `high` (100 RPS) requires support approval — request via email if you need it.' expires_at: type: string format: date-time description: Optional expiration timestamp. Omit for never-expires keys. role_id: type: string format: uuid description: Account-level role to assign. The key inherits this role's `account.*` permissions. To grant per-project access, add the key as a Project Member after creation. UpdateAPIKeyRequest: type: object description: At least one field is required. To change the role, rotate to a new key — role at create time is frozen. properties: name: type: string minLength: 1 maxLength: 128 rate_limit_tier: type: string enum: [standard, high] expires_at: type: string format: date-time is_active: type: boolean Invitation: type: object required: - id - type - email properties: id: type: string format: uuid type: type: string enum: [account, project] description: Account-level or project-level invitation account_id: type: string format: uuid account_name: type: string project_id: type: string format: uuid description: Set only when `type` is `project` project_name: type: string email: type: string format: email role_id: type: string format: uuid role_name: type: string invited_by_id: type: string format: uuid invited_by_email: type: string format: email invited_by_name: type: string expires_at: type: string format: date-time description: When the invitation auto-expires. Past this time, the accept link returns an error and the row is removed by a cleanup job. created_at: type: string format: date-time CreateInvitationRequest: type: object required: - email - role_id properties: email: type: string format: email description: Recipient's email address. Must not already be a member. role_id: type: string format: uuid description: 'Role the invitee will receive on acceptance. Must be `scope: account` for account invites or `scope: project` for project invites.' 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