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 - `GET /api/v1/public/pricing/database` — managed database pricing (plans, high availability, read replicas, extra storage) - `GET /api/v1/public/pricing/kubernetes` — managed Kubernetes pricing (worker node plans, HA control plane, storage nodes) 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 - name: Kubernetes description: Managed Kubernetes clusters — HA control planes, autoscaling node pools, kubeconfig access - name: Functions description: Deploy and manage serverless functions - name: Raff Apps description: Push-to-deploy web services, private services, workers, cron jobs, and one-off jobs on the Raff PaaS — with custom domains, environment variables, autoscaling, and per-second billing with spend caps - name: Managed Databases description: Fully managed PostgreSQL and Valkey databases with private VPC networking, automatic backups, and metrics - 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. Windows templates are only returned for `windows`; `standard` and `premium` exclude them. schema: type: string enum: [standard, premium, windows] - 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, windows] - 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/public/pricing/database: get: tags: - Catalog summary: List managed database pricing description: Returns managed database plans plus high availability, read replica, and extra storage pricing. High availability multiplies the plan price — an HA plan costs `price_per_month * (1 + price_multiplier)`. operationId: listDatabasePricing security: [] parameters: - name: engine in: query description: Filter by database engine schema: $ref: '#/components/schemas/DatabaseEngine' responses: '200': description: Managed database pricing content: application/json: schema: type: object properties: success: type: boolean example: true plans: type: array items: $ref: '#/components/schemas/DatabasePricingPlan' ha_pricing: type: array description: High availability pricing per engine items: $ref: '#/components/schemas/DatabaseHAPricing' replica_pricing: type: array description: Read replica pricing per engine items: $ref: '#/components/schemas/DatabaseReplicaPricing' storage_pricing: type: array description: Extra storage pricing per engine items: $ref: '#/components/schemas/DatabaseStoragePricing' '400': $ref: '#/components/responses/BadRequest' /api/v1/public/pricing/kubernetes: get: tags: - Catalog summary: List managed Kubernetes pricing description: Returns worker node plans plus HA control plane and storage node pricing. The standard control plane is free — `ha_pricing` is the optional highly available control plane. operationId: listKubernetesPricing security: [] responses: '200': description: Managed Kubernetes pricing content: application/json: schema: type: object properties: success: type: boolean example: true plans: type: array items: $ref: '#/components/schemas/K8sNodePlan' ha_pricing: $ref: '#/components/schemas/K8sHAPricing' storage_pricing: $ref: '#/components/schemas/K8sStoragePricing' '401': $ref: '#/components/responses/Unauthorized' '400': $ref: '#/components/responses/BadRequest' /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 on attach — you're already paying for the reserved IP. A VM can hold up to **4 public IPv4 addresses**. The first IPv4 on a VM becomes its primary address; additional IPv4s must be reserved IPs attached with `ip_id`. ## 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` Auto-assign is available only while the VM has no public IP of that family, and the IP is included with the VM at no extra charge. It is not reserved: detaching releases it back to the shared pool. ## Notes - Auto-assign (`type`) fails if the VM already has a public IP of the same family — attach a reserved IP (`ip_id`) to add more IPv4s, up to 4 total. - 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. A reserved IP returns to your reserved pool and **continues to be billed** until you release it via `DELETE /api/v1/ips/{id}/reserve`. An auto-assigned IP is released back to the shared pool. Use the `nic_id` from `GET /api/v1/vms/{id}/networks`. If you detach the VM's primary IPv4 while other IPv4s remain attached, the lowest-numbered remaining IPv4 becomes the new primary. 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' /api/v1/databases/engines: get: tags: - Managed Databases summary: List database engines description: | The database engine catalog: each engine with its display name, supported versions, and whether it is available to create. Engines listed with `available: false` (MySQL) are planned but cannot be created yet. operationId: listDatabaseEngines responses: '200': description: Engine catalog content: application/json: schema: type: object properties: success: type: boolean engines: type: array items: $ref: '#/components/schemas/DatabaseEngineInfo' '401': $ref: '#/components/responses/Unauthorized' /api/v1/databases/plans: get: tags: - Managed Databases summary: List database plans description: | Pricing tiers for managed databases, plus the high availability, read replica, and extra storage pricing. Use the plan `id` as `plan_id` when creating or scaling a database. The same catalog is available without authentication at `GET /api/v1/public/pricing/database`. operationId: listDatabasePlans parameters: - name: engine in: query description: Filter plans by engine schema: $ref: '#/components/schemas/DatabaseEngine' responses: '200': description: Plans and add-on pricing content: application/json: schema: type: object properties: success: type: boolean plans: type: array items: $ref: '#/components/schemas/DatabasePricingPlan' ha_pricing: type: array items: $ref: '#/components/schemas/DatabaseHAPricing' replica_pricing: type: array items: $ref: '#/components/schemas/DatabaseReplicaPricing' storage_pricing: type: array items: $ref: '#/components/schemas/DatabaseStoragePricing' '401': $ref: '#/components/responses/Unauthorized' /api/v1/databases: get: tags: - Managed Databases summary: List databases description: List all managed databases for the authenticated account. operationId: listDatabases responses: '200': description: List of databases content: application/json: schema: type: object properties: success: type: boolean databases: type: array items: $ref: '#/components/schemas/Database' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Managed Databases summary: Create database description: | Create a managed database. Provisioning is asynchronous — the database is created in status `pending` and moves through `deploying` to `running`; poll `GET /api/v1/databases/{database_id}` for progress. ## Networking Databases live on private VPC networking by default. Set `vpc_id` to place the private endpoint (`{database_id}.db.raffusercloud.com`) inside an existing VPC, or attach a VPC later via `POST /api/v1/databases/{database_id}/vpc`. Public access is optional and off by default. TLS is always required. ## Engines - **PostgreSQL** — pooled endpoint on port 6432 (PgBouncer, transaction mode) plus a direct endpoint on 5432. Nightly base backup with continuous WAL archiving for point-in-time restore. - **Valkey** — single TLS endpoint on port 6379. Nightly RDB snapshots plus on-demand snapshots. No read replicas — high availability adds a standby with automatic Sentinel failover. ## Pricing The plan sets the base price. High availability adds 70% of the tier price; storage above the tier allotment is $0.12/GB/month. See `GET /api/v1/public/pricing/database` for the full catalog. operationId: createDatabase parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateDatabaseRequest' responses: '201': description: Database created (status `pending` while provisioning) content: application/json: schema: type: object properties: success: type: boolean database: $ref: '#/components/schemas/Database' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '403': $ref: '#/components/responses/BillingValidationFailed' /api/v1/databases/{database_id}: get: tags: - Managed Databases summary: Get database description: Get details of a managed database. operationId: getDatabase parameters: - $ref: '#/components/parameters/DatabaseIDPath' responses: '200': description: Database details content: application/json: schema: type: object properties: success: type: boolean database: $ref: '#/components/schemas/Database' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' patch: tags: - Managed Databases summary: Rename database description: Update the display name of a database. Connection endpoints are based on the immutable `database_id` and do not change. operationId: updateDatabase parameters: - $ref: '#/components/parameters/DatabaseIDPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateDatabaseRequest' responses: '200': description: Updated database content: application/json: schema: type: object properties: success: type: boolean database: $ref: '#/components/schemas/Database' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' delete: tags: - Managed Databases summary: Delete database description: | Permanently delete a managed database and its backups. This cannot be undone. If the database has an active subscription, the remaining prepaid balance is refunded pro-rata to your account balance. operationId: deleteDatabase parameters: - $ref: '#/components/parameters/DatabaseIDPath' responses: '200': description: Deletion started content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/scale: post: tags: - Managed Databases summary: Scale database description: | Change the plan, storage, high availability, or read replica count of a running database. - **Plan** — the new plan must be the same engine. Its included storage sets a floor: storage never shrinks below the new tier's allotment. - **Storage** — `storage_gb` can only grow. Omit (or `0`) to keep the current size. - **High availability** — set `set_ha: true` and put the desired state in `has_ha`. When `set_ha` is `false` or omitted, HA is unchanged. - **Read replicas** — `replica_count` is always applied: pass your current count when you are not changing it. PostgreSQL only; Valkey rejects a count above 0. operationId: scaleDatabase parameters: - $ref: '#/components/parameters/DatabaseIDPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ScaleDatabaseRequest' responses: '200': description: Updated database content: application/json: schema: type: object properties: success: type: boolean database: $ref: '#/components/schemas/Database' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/connection: get: tags: - Managed Databases summary: Get connection details description: | Connection details for the database: host, port, username, database name, and a ready-to-use connection URI. The `password` field is empty unless you pass `reveal=true`. TLS is always required. operationId: getDatabaseConnection parameters: - $ref: '#/components/parameters/DatabaseIDPath' - name: reveal in: query description: Set to `true` to include the password in the response and connection URI schema: type: boolean default: false responses: '200': description: Connection details content: application/json: schema: allOf: - type: object properties: success: type: boolean - $ref: '#/components/schemas/DatabaseConnection' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/credentials/rotate: post: tags: - Managed Databases summary: Rotate credentials description: | Generate a new password for the database user. Existing connections keep working until they disconnect; new connections must use the new password. Fetch it via `GET /api/v1/databases/{database_id}/connection?reveal=true`. operationId: rotateDatabaseCredentials parameters: - $ref: '#/components/parameters/DatabaseIDPath' responses: '200': description: Credentials rotated content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/vpc: post: tags: - Managed Databases summary: Connect database to VPC description: | Attach a database created without a VPC to one of your VPCs. The private endpoint `{database_id}.db.raffusercloud.com` becomes reachable from inside that VPC. operationId: connectDatabaseVPC parameters: - $ref: '#/components/parameters/DatabaseIDPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ConnectDatabaseVPCRequest' responses: '200': description: Database connected to the VPC content: application/json: schema: type: object properties: success: type: boolean database: $ref: '#/components/schemas/Database' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/public-access: post: tags: - Managed Databases summary: Set public access description: | Open or close public access to the database. When enabled, the database is reachable at `{database_id}.public.db.raffusercloud.com` on a port assigned from the reserved range 25060–26060 (`public_port` on the database object). For PostgreSQL, `public_port` is the pooled endpoint and `public_port + 1` is the direct endpoint. TLS is always required. `allowlist` restricts which source CIDRs may connect. Omit it to keep the stored list; pass `[]` to allow all sources. Re-post `enabled: true` with a new `allowlist` to update the restriction in place. operationId: setDatabasePublicAccess parameters: - $ref: '#/components/parameters/DatabaseIDPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DatabasePublicAccessRequest' responses: '200': description: Updated database content: application/json: schema: type: object properties: success: type: boolean database: $ref: '#/components/schemas/Database' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/metrics: get: tags: - Managed Databases summary: Get metrics description: | Latest usage snapshot: CPU, memory, storage, connections, and replication lag. Metrics refresh about every 60 seconds; zero values mean not measured yet. operationId: getDatabaseMetrics parameters: - $ref: '#/components/parameters/DatabaseIDPath' responses: '200': description: Latest metrics snapshot content: application/json: schema: allOf: - type: object properties: success: type: boolean - $ref: '#/components/schemas/DatabaseMetrics' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/metrics/history: get: tags: - Managed Databases summary: Get metrics history description: Time-series metrics over a window, bucketed for charting. operationId: getDatabaseMetricsHistory parameters: - $ref: '#/components/parameters/DatabaseIDPath' - name: window_hours in: query description: History window in hours schema: type: integer default: 24 responses: '200': description: Metric points, oldest first content: application/json: schema: type: object properties: success: type: boolean points: type: array items: $ref: '#/components/schemas/DatabaseMetricsPoint' bucket_seconds: type: integer description: Seconds each point covers '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/slow-queries: get: tags: - Managed Databases summary: Get slow queries description: | The slowest recent queries. - **PostgreSQL** — aggregated statistics from `pg_stat_statements`: each row is one normalized query with its call count, mean, and total time. - **Valkey** — individual slow commands from `SLOWLOG`: `calls` is 1 per entry and `rows` is not applicable. `collecting` indicates whether statistics collection is active yet. operationId: getDatabaseSlowQueries parameters: - $ref: '#/components/parameters/DatabaseIDPath' - name: limit in: query description: Maximum number of entries to return schema: type: integer default: 20 responses: '200': description: Slow query entries content: application/json: schema: type: object properties: success: type: boolean queries: type: array items: $ref: '#/components/schemas/DatabaseSlowQuery' collecting: type: boolean description: Whether slow-query statistics are being collected '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/logs: get: tags: - Managed Databases summary: Get logs description: Recent engine log lines, newest last. operationId: getDatabaseLogs parameters: - $ref: '#/components/parameters/DatabaseIDPath' - name: tail in: query description: Number of log lines to return schema: type: integer default: 200 responses: '200': description: Log lines content: application/json: schema: type: object properties: success: type: boolean lines: type: array items: type: string '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/extensions: get: tags: - Managed Databases summary: List extensions description: | The PostgreSQL extension allowlist with the installed state of each extension. Valkey has no extensions — the list is empty. operationId: listDatabaseExtensions parameters: - $ref: '#/components/parameters/DatabaseIDPath' responses: '200': description: Extension catalog with installed state content: application/json: schema: type: object properties: success: type: boolean extensions: type: array items: $ref: '#/components/schemas/DatabaseExtension' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' post: tags: - Managed Databases summary: Enable or disable extension description: Install or remove a PostgreSQL extension from the allowlist. PostgreSQL only. operationId: setDatabaseExtension parameters: - $ref: '#/components/parameters/DatabaseIDPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SetDatabaseExtensionRequest' responses: '200': description: Extension updated 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/databases/{database_id}/backups: get: tags: - Managed Databases summary: List backups description: | All backups for the database: nightly automatic backups (`base`) and on-demand backups (`on_demand`). operationId: listDatabaseBackups parameters: - $ref: '#/components/parameters/DatabaseIDPath' responses: '200': description: List of backups content: application/json: schema: type: object properties: success: type: boolean backups: type: array items: $ref: '#/components/schemas/DatabaseBackup' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' post: tags: - Managed Databases summary: Create backup description: | Start an on-demand backup — a base backup for PostgreSQL, an RDB snapshot for Valkey. The backup runs asynchronously; poll the list until its status is `completed`. operationId: createDatabaseBackup parameters: - $ref: '#/components/parameters/DatabaseIDPath' responses: '201': description: Backup started (status `running`) content: application/json: schema: type: object properties: success: type: boolean backup: $ref: '#/components/schemas/DatabaseBackup' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/databases/{database_id}/resume: post: tags: - Managed Databases summary: Resume a paused database description: | Wake a free-tier database that was suspended for idleness (no client connections for 7 days). The database returns through `deploying` and reaches `running` in about a minute; all data is preserved. operationId: resumeDatabase parameters: - name: database_id in: path required: true schema: type: string description: Database short ID responses: '200': description: Resume started content: application/json: schema: type: object properties: database: $ref: '#/components/schemas/Database' '400': description: The database is not paused '404': description: Database not found /api/v1/databases/{database_id}/restore: post: tags: - Managed Databases summary: Restore to a new database description: | Restore a database into a NEW instance. In-place restore is not supported — it would destroy the backup timeline it restores from — so `clone_to_new` must be `true`. Choose the restore point with `backup_id` (a specific completed backup) or, for PostgreSQL, `pitr_timestamp` (any point in time covered by continuous WAL archiving). Omit both to restore the latest available state. The clone inherits the source's plan, storage, VPC, and project, starts with no read replicas, and bills pay-as-you-go. High availability is an explicit choice via `ha_enabled` — never inherited. If `new_name` is taken, the platform auto-suffixes it (`-2`, `-3`, …). The clone provisions asynchronously; poll `GET /api/v1/databases/{new_database_id}` like any create. operationId: restoreDatabase parameters: - $ref: '#/components/parameters/DatabaseIDPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RestoreDatabaseRequest' responses: '200': description: Restore started content: application/json: schema: type: object properties: success: type: boolean new_database_id: type: string description: Short ID of the new database being created '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions: get: tags: - Functions summary: List functions description: List all serverless functions for the authenticated account. operationId: listFunctions parameters: - name: status in: query description: "Filter by status: `pending`, `building`, `live`, `paused`, `error`." schema: type: string enum: [pending, building, live, paused, error] responses: '200': description: List of functions content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: functions: type: array items: $ref: '#/components/schemas/Function' total: type: integer '401': $ref: '#/components/responses/Unauthorized' post: tags: - Functions summary: Create function description: | Create a serverless function. The function serves at `https://{slug}.fn.raffusercloud.com` — the slug is your name plus a short random suffix, so names never collide across accounts. Functions unlock after your first top-up of $5 or more (the money stays in your balance). Deploy code afterwards with a source upload and a deployment, or use a starter template. Timeouts above 3600s require `extended_timeout: true` (max 86400s / 24h). Long runs bill wall-clock memory while they execute — there is no extra fee. operationId: createFunction parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateFunctionRequest' responses: '201': description: Function created (status `pending` until first deployment) content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: function: $ref: '#/components/schemas/Function' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': description: Account not funded — Functions unlock after the first top-up of $5 or more '409': description: A function with this name already exists in the account /api/v1/functions/usage: get: tags: - Functions summary: Get usage & estimated charge description: | Current calendar-month usage across the account's two paid meters (memory GB-seconds and active-CPU vCPU-seconds), the monthly free tier remaining, AI Lambda-conversions used, and the estimated charge month-to-date (only usage beyond the free tier bills). Requests and egress are free and never metered for billing. operationId: getFunctionsUsage parameters: - name: function_id in: query description: Scope to one function (pre-free-tier view; the free tier is account-wide) schema: type: string responses: '200': description: Usage summary content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/FunctionsUsage' '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/spend-settings: get: tags: - Functions summary: Get spend cap settings description: | The monthly spend cap is ON by default ($50/month). At the cap your functions pause — they stop serving instead of charging you — and resume automatically next month or when you raise the cap. operationId: getFunctionsSpendSettings responses: '200': description: Spend cap settings content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: settings: $ref: '#/components/schemas/FunctionsSpendSettings' '401': $ref: '#/components/responses/Unauthorized' patch: tags: - Functions summary: Update spend cap settings operationId: updateFunctionsSpendSettings requestBody: required: true content: application/json: schema: type: object properties: enabled: type: boolean monthly_cap_usd: type: number description: Monthly maximum in USD (0–1,000,000) responses: '200': description: Updated settings content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: settings: $ref: '#/components/schemas/FunctionsSpendSettings' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/import/lambda: post: tags: - Functions summary: Convert AWS Lambda source description: | Convert AWS Lambda handler code to a Raff function. Stateless — nothing is stored; review the output, then create a function and deploy the returned files. Two modes: - `rewrite` (default) — AI rewrites the handler to a portable standard handler (FastAPI / Web Fetch / net/http). Every guess is flagged in `warnings`. 5 free per calendar month, then $0.30 per conversion from your balance. Up to 64KB of source. - `adapter` — your code ships unchanged plus a small static shim that presents API-Gateway-style events. Instant, free, unlimited. Config always maps deterministically: memory/timeout, env vars to `raff_toml`, EventBridge `rate()` schedules to cron trigger proposals. IAM, VPC, layers and stream sources are listed in `out_of_scope` with alternatives. operationId: convertLambda requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ConvertLambdaRequest' responses: '200': description: Conversion result (review before deploying) content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/ConvertLambdaResult' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': description: AI rewrite requires a funded account (adapter mode works without) /api/v1/functions/{function_id}: get: tags: - Functions summary: Get function description: Fetch one function by ID, URL slug, or name. operationId: getFunction parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Function details content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: function: $ref: '#/components/schemas/Function' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' patch: tags: - Functions summary: Update function settings description: | Partially update runtime settings (memory, timeout, extended timeout, scale bounds, description). When the function is live the change rolls out as a new revision with zero downtime. operationId: updateFunction parameters: - $ref: '#/components/parameters/FunctionRef' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateFunctionRequest' responses: '200': description: Updated function content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: function: $ref: '#/components/schemas/Function' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' delete: tags: - Functions summary: Delete function description: | Delete a function and all its revisions. Deletion is asynchronous; the function's name and URL are released once it completes. operationId: deleteFunction parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Deletion accepted '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/pause: post: tags: - Functions summary: Pause function description: Stop serving traffic. The image and revisions are kept; resume any time. operationId: pauseFunction parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Function paused '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/resume: post: tags: - Functions summary: Resume function description: Re-deploy the current revision and start serving again. operationId: resumeFunction parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Function resuming (status `building` until live) '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/source-upload: post: tags: - Functions summary: Request source upload URL description: | Returns a presigned URL to PUT a source zip (max 100MB). After uploading, start a deployment with the returned `source_blob_key`. operationId: requestFunctionSourceUpload parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Presigned upload slot content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/FunctionSourceUpload' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/deployments: get: tags: - Functions summary: List deployments operationId: listFunctionDeployments parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Deployments, newest first content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: deployments: type: array items: $ref: '#/components/schemas/FunctionDeployment' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Functions summary: Create deployment description: | Build and roll out new source. Provide exactly one source: - `source_blob_key` — from a completed source upload - `template` — a starter template id (`python`, `typescript`, `node`, `go`) - `files` — inline source files (small console edits) Functions connected to GitHub deploy their branch head when no source is given. Buildpacks detect the language; a `Dockerfile` at the source root switches to a container build. The container must listen on the `PORT` environment variable. operationId: createFunctionDeployment parameters: - $ref: '#/components/parameters/FunctionRef' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateFunctionDeploymentRequest' responses: '201': description: Deployment queued content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: deployment: $ref: '#/components/schemas/FunctionDeployment' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/deployments/{deployment_id}: get: tags: - Functions summary: Get deployment operationId: getFunctionDeployment parameters: - $ref: '#/components/parameters/FunctionRef' - name: deployment_id in: path required: true schema: type: string format: uuid responses: '200': description: Deployment status content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: deployment: $ref: '#/components/schemas/FunctionDeployment' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/deployments/{deployment_id}/logs: get: tags: - Functions summary: Get build logs operationId: getFunctionDeploymentLogs parameters: - $ref: '#/components/parameters/FunctionRef' - name: deployment_id in: path required: true schema: type: string format: uuid responses: '200': description: Build log lines (poll until `complete`) content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: lines: type: array items: $ref: '#/components/schemas/FunctionBuildLogLine' complete: type: boolean '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/{function_id}/revisions: get: tags: - Functions summary: List revisions description: Immutable deploy history. Roll back to any revision instantly — revisions are free. operationId: listFunctionRevisions parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Revisions, newest first content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: revisions: type: array items: $ref: '#/components/schemas/FunctionRevision' '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/{function_id}/rollback: post: tags: - Functions summary: Roll back to a revision description: Re-deploys the target revision's image — no rebuild, zero downtime. operationId: rollbackFunction parameters: - $ref: '#/components/parameters/FunctionRef' requestBody: required: true content: application/json: schema: type: object required: [revision_id] properties: revision_id: type: string format: uuid responses: '200': description: Rollback deployment queued '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/source: get: tags: - Functions summary: Get revision source description: Download URL (and inline files for small sources) of a revision's source zip. operationId: getFunctionSource parameters: - $ref: '#/components/parameters/FunctionRef' - name: revision_id in: query description: Defaults to the current revision schema: type: string format: uuid responses: '200': description: Source download content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: download_url: type: string files: type: array items: $ref: '#/components/schemas/FunctionSourceFile' inline_truncated: type: boolean '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/logs: get: tags: - Functions summary: Get runtime logs description: Stdout/stderr of the running function (14-day retention, included free). operationId: listFunctionLogs parameters: - $ref: '#/components/parameters/FunctionRef' - name: since in: query description: RFC3339 start time (default one hour ago) schema: type: string format: date-time - name: until in: query schema: type: string format: date-time - name: level in: query schema: type: string enum: [info, warn, error, debug] - name: q in: query description: Substring filter schema: type: string - name: limit in: query schema: type: integer default: 500 maximum: 1000 responses: '200': description: Log lines, oldest first content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: lines: type: array items: $ref: '#/components/schemas/FunctionLogLine' '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/{function_id}/metrics: get: tags: - Functions summary: Get metrics description: Invocations, latency percentiles and error rate (included free). operationId: getFunctionMetrics parameters: - $ref: '#/components/parameters/FunctionRef' - name: window in: query schema: type: string enum: [1h, 24h, 7d] default: 1h responses: '200': description: Metric series + summary numbers content: application/json: schema: type: object properties: success: type: boolean data: $ref: '#/components/schemas/FunctionMetrics' '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/{function_id}/invocations: get: tags: - Functions summary: List recent invocations operationId: listFunctionInvocations parameters: - $ref: '#/components/parameters/FunctionRef' - name: limit in: query schema: type: integer default: 20 responses: '200': description: Recent requests, newest first content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: invocations: type: array items: $ref: '#/components/schemas/FunctionInvocation' '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/{function_id}/env: get: tags: - Functions summary: List environment variables description: Secret values are masked; use the reveal endpoint to read them. operationId: listFunctionEnvVars parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Environment variables content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: env_vars: type: array items: $ref: '#/components/schemas/FunctionEnvVar' '401': $ref: '#/components/responses/Unauthorized' put: tags: - Functions summary: Set environment variable description: | Create or update one variable. Live functions roll the change out as a new revision with zero downtime. Keys prefixed `PORT`, `K_` or `RAFF_` are reserved. operationId: setFunctionEnvVar parameters: - $ref: '#/components/parameters/FunctionRef' requestBody: required: true content: application/json: schema: type: object required: [key, value] properties: key: type: string value: type: string is_secret: type: boolean responses: '200': description: Variable set (response includes the config redeploy when one was started) '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/{function_id}/env/reveal: get: tags: - Functions summary: List environment variables with values description: Returns secret values in cleartext. Requires manage permission; access is audited. operationId: revealFunctionEnvVars parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Environment variables with values content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: env_vars: type: array items: $ref: '#/components/schemas/FunctionEnvVar' '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/{function_id}/env/{key}: delete: tags: - Functions summary: Delete environment variable operationId: deleteFunctionEnvVar parameters: - $ref: '#/components/parameters/FunctionRef' - name: key in: path required: true schema: type: string responses: '200': description: Variable deleted (config redeploy rolls it out when live) '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/triggers: get: tags: - Functions summary: List triggers operationId: listFunctionTriggers parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Triggers (HTTP is always on and not listed) content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: triggers: type: array items: $ref: '#/components/schemas/FunctionTrigger' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Functions summary: Create trigger description: | Schedule or subscribe the function. `cron` uses a 5-field expression with an optional IANA `timezone`; `once` fires a single time at `run_at`; `s3` fires on Object Storage bucket events (optionally filtered by key prefix/suffix). Deliveries are signed CloudEvents (HMAC `X-Raff-Signature`) with 3 retry attempts. Triggers are free. operationId: createFunctionTrigger parameters: - $ref: '#/components/parameters/FunctionRef' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateFunctionTriggerRequest' responses: '201': description: Trigger created content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: trigger: $ref: '#/components/schemas/FunctionTrigger' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/{function_id}/triggers/{trigger_id}: patch: tags: - Functions summary: Update trigger operationId: updateFunctionTrigger parameters: - $ref: '#/components/parameters/FunctionRef' - name: trigger_id in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object properties: enabled: type: boolean cron_expression: type: string timezone: type: string run_at: type: string format: date-time payload_json: type: string responses: '200': description: Trigger updated '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' delete: tags: - Functions summary: Delete trigger operationId: deleteFunctionTrigger parameters: - $ref: '#/components/parameters/FunctionRef' - name: trigger_id in: path required: true schema: type: string format: uuid responses: '200': description: Trigger deleted '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/bindings: get: tags: - Functions summary: List bindings operationId: listFunctionBindings parameters: - $ref: '#/components/parameters/FunctionRef' responses: '200': description: Bindings content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: bindings: type: array items: $ref: '#/components/schemas/FunctionBinding' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Functions summary: Create binding description: | Wire a Raff-managed resource into the function. A binding mints a scoped credential (its own database role / S3 key — independent of the resource's main credentials) and injects it as read-only system env vars (`{PREFIX}_URL`, `{PREFIX}_HOST`, … for databases; `{PREFIX}_ENDPOINT`, `{PREFIX}_ACCESS_KEY`, … for buckets). Database bindings connect over Raff's private service fabric — traffic never leaves the platform network. Live functions roll a zero-downtime config redeploy. operationId: createFunctionBinding parameters: - $ref: '#/components/parameters/FunctionRef' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateFunctionBindingRequest' responses: '200': description: Binding created content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: binding: $ref: '#/components/schemas/FunctionBinding' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/functions/{function_id}/bindings/{binding_id}: delete: tags: - Functions summary: Delete binding description: Revokes the scoped credential and removes its env vars. operationId: deleteFunctionBinding parameters: - $ref: '#/components/parameters/FunctionRef' - name: binding_id in: path required: true schema: type: string format: uuid responses: '200': description: Binding deleted '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/bindings/{binding_id}/rotate: post: tags: - Functions summary: Rotate binding credentials description: | Mints a fresh credential and refreshes the injected env vars; the old credential stops working immediately. Zero-downtime redeploy. operationId: rotateFunctionBinding parameters: - $ref: '#/components/parameters/FunctionRef' - name: binding_id in: path required: true schema: type: string format: uuid responses: '200': description: Rotated content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: binding: $ref: '#/components/schemas/FunctionBinding' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/functions/{function_id}/trigger-runs: get: tags: - Functions summary: List trigger runs description: Delivery history for the function's triggers (30-day retention). operationId: listFunctionTriggerRuns parameters: - $ref: '#/components/parameters/FunctionRef' - name: trigger_id in: query schema: type: string format: uuid - name: limit in: query schema: type: integer default: 20 responses: '200': description: Runs, newest first content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: runs: type: array items: $ref: '#/components/schemas/FunctionTriggerRun' '401': $ref: '#/components/responses/Unauthorized' /api/v1/public/functions/runtimes: get: tags: - Functions summary: List runtimes (public) description: The managed runtime catalog. No authentication required. operationId: listFunctionRuntimes security: [] responses: '200': description: Available runtimes content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: runtimes: type: array items: $ref: '#/components/schemas/FunctionRuntimeInfo' /api/v1/public/pricing/functions: get: tags: - Functions summary: Get Functions pricing (public) description: | Two paid meters — memory (GB-seconds, wall-clock) and active CPU (vCPU-seconds) — plus the monthly account-wide free tier. Requests, egress, scheduling, logs and metrics are free. No authentication required. operationId: getFunctionsPricing security: [] responses: '200': description: Active pricing content: application/json: schema: type: object properties: success: type: boolean data: type: object properties: pricing: type: array items: $ref: '#/components/schemas/FunctionsPricing' /api/v1/apps/tiers: get: tags: - Raff Apps summary: List Apps tiers description: The Apps pricing catalog — compute sizes and prices. No authentication required. operationId: listAppTiers security: [] parameters: - name: region in: query schema: type: string responses: '200': description: Available tiers content: application/json: schema: type: object properties: success: type: boolean tiers: type: array items: $ref: '#/components/schemas/AppTier' /api/v1/apps/services: get: tags: - Raff Apps summary: List app services description: List app services for the authenticated account. operationId: listAppServices parameters: - name: project_id in: query schema: type: string format: uuid - name: service_type in: query description: "Filter by type: `web`, `private`, `worker`, `cron`, or `job`." schema: type: string enum: [web, private, worker, cron, job] - name: status in: query schema: type: string responses: '200': description: List of app services content: application/json: schema: type: object properties: success: type: boolean services: type: array items: $ref: '#/components/schemas/AppService' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Raff Apps summary: Create app service description: | Create an app service. Provisioning is asynchronous — the service is created in status `pending` and moves through `building` and `deploying` to `live`; poll `GET /api/v1/apps/services/{service_id}` for progress. Choose a `service_type`: `web` (public HTTPS URL), `private` (in-cluster only), `worker` (long-running, no URL), `cron` (scheduled), or `job` (one-off). Pick a `source_type`: `git` (a connected GitHub repo), `image` (a prebuilt image), `template` (a starter), or `compose`. Deploy code afterwards with a source upload and a deployment, or set `deploy_now` for image sources. operationId: createAppService parameters: - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateAppServiceRequest' responses: '201': description: App service created (status `pending` while provisioning) content: application/json: schema: type: object properties: success: type: boolean service: $ref: '#/components/schemas/AppService' deployment_id: type: string format: uuid description: Set when a deployment was queued at create time '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' '403': $ref: '#/components/responses/BillingValidationFailed' '409': description: A service with this name already exists in the project /api/v1/apps/services/{service_id}: get: tags: - Raff Apps summary: Get app service operationId: getAppService parameters: - $ref: '#/components/parameters/AppServiceRef' responses: '200': description: App service details content: application/json: schema: type: object properties: success: type: boolean service: $ref: '#/components/schemas/AppService' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' delete: tags: - Raff Apps summary: Delete app service description: | Delete an app service. Deletion is asynchronous; the name and URL are released once it completes. operationId: deleteAppService parameters: - $ref: '#/components/parameters/AppServiceRef' responses: '200': description: Deletion accepted content: application/json: schema: type: object properties: success: type: boolean accepted: type: boolean '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/pause: post: tags: - Raff Apps summary: Pause app service description: Scale the service to zero and stop billing for replicas. Cron services are suspended. operationId: pauseAppService parameters: - $ref: '#/components/parameters/AppServiceRef' responses: '200': description: Paused service content: application/json: schema: type: object properties: success: type: boolean service: $ref: '#/components/schemas/AppService' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/resume: post: tags: - Raff Apps summary: Resume app service description: Bring a paused service back to its configured replica count. operationId: resumeAppService parameters: - $ref: '#/components/parameters/AppServiceRef' responses: '200': description: Resumed service content: application/json: schema: type: object properties: success: type: boolean service: $ref: '#/components/schemas/AppService' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/scale: post: tags: - Raff Apps summary: Scale app service description: | Update the replica count, autoscaling bounds, or scale-to-zero. Live services apply the change with no downtime. operationId: scaleAppService parameters: - $ref: '#/components/parameters/AppServiceRef' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ScaleAppServiceRequest' responses: '200': description: Updated service content: application/json: schema: type: object properties: success: type: boolean service: $ref: '#/components/schemas/AppService' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/source-upload: post: tags: - Raff Apps summary: Request source upload URL description: | Get a presigned URL to upload a source tarball (gzipped). After the upload completes, create a deployment with the returned `blob_key`. operationId: requestAppSourceUpload parameters: - $ref: '#/components/parameters/AppServiceRef' responses: '200': description: Presigned upload slot content: application/json: schema: type: object properties: success: type: boolean upload_url: type: string blob_key: type: string expires_in_seconds: type: integer '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/deployments: get: tags: - Raff Apps summary: List deployments operationId: listAppDeployments parameters: - $ref: '#/components/parameters/AppServiceRef' - name: limit in: query schema: type: integer default: 20 responses: '200': description: Deployments, newest first content: application/json: schema: type: object properties: success: type: boolean deployments: type: array items: $ref: '#/components/schemas/AppDeployment' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Raff Apps summary: Create deployment description: | Build and roll out a new deployment. Provide a `source_blob_key` from a completed source upload, or an `image_ref` for a prebuilt image. Git-connected services deploy their branch head when neither is given. operationId: createAppDeployment parameters: - $ref: '#/components/parameters/AppServiceRef' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateAppDeploymentRequest' responses: '201': description: Deployment queued content: application/json: schema: type: object properties: success: type: boolean deployment: $ref: '#/components/schemas/AppDeployment' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/deployments/{deployment_id}: get: tags: - Raff Apps summary: Get deployment operationId: getAppDeployment parameters: - $ref: '#/components/parameters/AppDeploymentIDPath' responses: '200': description: Deployment status content: application/json: schema: type: object properties: success: type: boolean deployment: $ref: '#/components/schemas/AppDeployment' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/deployments/{deployment_id}/logs: get: tags: - Raff Apps summary: Get deployment build logs operationId: getAppDeploymentLogs parameters: - $ref: '#/components/parameters/AppDeploymentIDPath' responses: '200': description: Build log lines content: application/json: schema: type: object properties: success: type: boolean lines: type: array items: $ref: '#/components/schemas/AppBuildLogLine' complete: type: boolean '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/rollback: post: tags: - Raff Apps summary: Roll back to a deployment description: | Pin the service back to an earlier deployment's image. Creates a new deployment from the target's digest and config snapshot. operationId: rollbackAppService parameters: - $ref: '#/components/parameters/AppServiceRef' requestBody: required: true content: application/json: schema: type: object required: [target_deployment_id] properties: target_deployment_id: type: string format: uuid responses: '201': description: Rollback deployment queued content: application/json: schema: type: object properties: success: type: boolean deployment: $ref: '#/components/schemas/AppDeployment' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/logs: get: tags: - Raff Apps summary: List runtime logs operationId: listAppLogs parameters: - $ref: '#/components/parameters/AppServiceRef' - name: level in: query schema: type: string - name: search in: query schema: type: string - name: since in: query schema: type: string format: date-time - name: until in: query schema: type: string format: date-time - name: limit in: query schema: type: integer default: 200 responses: '200': description: Runtime log lines content: application/json: schema: type: object properties: success: type: boolean lines: type: array items: $ref: '#/components/schemas/AppLogLine' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/metrics: get: tags: - Raff Apps summary: Get service metrics operationId: getAppServiceMetrics parameters: - $ref: '#/components/parameters/AppServiceRef' - name: window in: query description: "Time window, e.g. `1h`, `24h`." schema: type: string responses: '200': description: Metric series content: application/json: schema: type: object properties: success: type: boolean series: type: array items: $ref: '#/components/schemas/AppMetricSeries' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/env: get: tags: - Raff Apps summary: List environment variables description: Secret values are masked; use the reveal endpoint to read them. operationId: listAppEnvVars parameters: - $ref: '#/components/parameters/AppServiceRef' responses: '200': description: Environment variables content: application/json: schema: type: object properties: success: type: boolean env_vars: type: array items: $ref: '#/components/schemas/AppEnvVar' '401': $ref: '#/components/responses/Unauthorized' put: tags: - Raff Apps summary: Set environment variable description: | Create or update one variable. By default the change rolls out to running replicas. Set `redeploy: false` to defer it to the next deploy. operationId: setAppEnvVar parameters: - $ref: '#/components/parameters/AppServiceRef' requestBody: required: true content: application/json: schema: type: object required: [key, value] properties: key: type: string value: type: string is_secret: type: boolean redeploy: type: boolean default: true responses: '200': description: Variable set content: application/json: schema: type: object properties: success: type: boolean env_vars: type: array items: $ref: '#/components/schemas/AppEnvVar' deployment_id: type: string format: uuid '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/apps/services/{service_id}/env/bulk: put: tags: - Raff Apps summary: Bulk set environment variables description: Upsert many variables at once (an `.env` paste). operationId: bulkSetAppEnvVars parameters: - $ref: '#/components/parameters/AppServiceRef' requestBody: required: true content: application/json: schema: type: object required: [vars] properties: vars: type: array items: type: object required: [key, value] properties: key: type: string value: type: string is_secret: type: boolean redeploy: type: boolean default: true responses: '200': description: Variables set content: application/json: schema: type: object properties: success: type: boolean env_vars: type: array items: $ref: '#/components/schemas/AppEnvVar' deployment_id: type: string format: uuid '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/apps/services/{service_id}/env/reveal: get: tags: - Raff Apps summary: List environment variables with values description: Returns secret values in cleartext. Requires manage permission; access is audited. operationId: revealAppEnvVars parameters: - $ref: '#/components/parameters/AppServiceRef' responses: '200': description: Environment variables with values content: application/json: schema: type: object properties: success: type: boolean env_vars: type: array items: $ref: '#/components/schemas/AppEnvVar' '401': $ref: '#/components/responses/Unauthorized' /api/v1/apps/services/{service_id}/env/{key}: delete: tags: - Raff Apps summary: Delete environment variable operationId: deleteAppEnvVar parameters: - $ref: '#/components/parameters/AppServiceRef' - name: key in: path required: true schema: type: string responses: '200': description: Variable deleted content: application/json: schema: type: object properties: success: type: boolean env_vars: type: array items: $ref: '#/components/schemas/AppEnvVar' deployment_id: type: string format: uuid '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/domains: get: tags: - Raff Apps summary: List custom domains operationId: listAppCustomDomains parameters: - $ref: '#/components/parameters/AppServiceRef' responses: '200': description: Custom domains content: application/json: schema: type: object properties: success: type: boolean domains: type: array items: $ref: '#/components/schemas/AppCustomDomain' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' post: tags: - Raff Apps summary: Add custom domain description: | Attach a custom domain to a web service. The response carries the DNS records to create — a CNAME to `cname_target` and a verification TXT record. Verification and TLS issuance run automatically once the DNS resolves. operationId: addAppCustomDomain parameters: - $ref: '#/components/parameters/AppServiceRef' requestBody: required: true content: application/json: schema: type: object required: [domain] properties: domain: type: string responses: '201': description: Domain added (status `pending_dns`) content: application/json: schema: type: object properties: success: type: boolean domain: $ref: '#/components/schemas/AppCustomDomain' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/domains/{domain_id}: delete: tags: - Raff Apps summary: Delete custom domain operationId: deleteAppCustomDomain parameters: - $ref: '#/components/parameters/AppDomainIDPath' responses: '200': description: Domain removed content: application/json: schema: type: object properties: success: type: boolean accepted: type: boolean '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/domains/{domain_id}/retry: post: tags: - Raff Apps summary: Retry domain verification description: Re-check DNS and re-attempt certificate issuance for a domain. operationId: retryAppDomainVerification parameters: - $ref: '#/components/parameters/AppDomainIDPath' responses: '200': description: Verification re-queued content: application/json: schema: type: object properties: success: type: boolean domain: $ref: '#/components/schemas/AppCustomDomain' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/jobs/run: post: tags: - Raff Apps summary: Trigger a manual job run description: Start a run of a cron or one-off job service now. operationId: runAppJob parameters: - $ref: '#/components/parameters/AppServiceRef' responses: '201': description: Run started content: application/json: schema: type: object properties: success: type: boolean run: $ref: '#/components/schemas/AppJobRun' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/services/{service_id}/jobs/runs: get: tags: - Raff Apps summary: List job runs operationId: listAppJobRuns parameters: - $ref: '#/components/parameters/AppServiceRef' responses: '200': description: Run history, newest first content: application/json: schema: type: object properties: success: type: boolean runs: type: array items: $ref: '#/components/schemas/AppJobRun' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/jobs/runs/{run_id}/cancel: post: tags: - Raff Apps summary: Cancel a job run operationId: cancelAppJobRun parameters: - name: run_id in: path required: true schema: type: string format: uuid responses: '200': description: Run canceled content: application/json: schema: type: object properties: success: type: boolean run: $ref: '#/components/schemas/AppJobRun' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /api/v1/apps/usage: get: tags: - Raff Apps summary: Get Apps usage description: Month-to-date replica-hours and estimated charge per service. operationId: getAppsUsage parameters: - name: service_id in: query description: Scope to a single service (omit for all services). schema: type: string - name: period in: query schema: type: string default: current responses: '200': description: Usage and estimated charge content: application/json: schema: $ref: '#/components/schemas/AppUsage' '401': $ref: '#/components/responses/Unauthorized' /api/v1/apps/spend-settings: get: tags: - Raff Apps summary: Get spend cap settings operationId: getAppSpendSettings responses: '200': description: Spend cap settings content: application/json: schema: type: object properties: success: type: boolean settings: $ref: '#/components/schemas/AppSpendSettings' '401': $ref: '#/components/responses/Unauthorized' patch: tags: - Raff Apps summary: Update spend cap settings description: | Set the account's monthly Apps spend cap. When the cap is reached services pause automatically until the next period or the cap is raised. operationId: updateAppSpendSettings requestBody: required: true content: application/json: schema: type: object required: [enabled, monthly_cap_usd] properties: enabled: type: boolean monthly_cap_usd: type: number cap_action: type: string enum: [pause] responses: '200': description: Updated settings content: application/json: schema: type: object properties: success: type: boolean settings: $ref: '#/components/schemas/AppSpendSettings' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /api/v1/public/pricing/apps: get: tags: - Raff Apps summary: Get Apps pricing (public) description: The Apps tier catalog — compute sizes and prices. No authentication required. operationId: getAppsPricing security: [] responses: '200': description: Available tiers content: application/json: schema: type: object properties: success: type: boolean tiers: type: array items: $ref: '#/components/schemas/AppTier' # --------------------------------------------------------------------------- # Kubernetes — managed clusters # --------------------------------------------------------------------------- /api/v1/k8s/versions: get: tags: - Kubernetes summary: List Kubernetes versions description: Kubernetes versions available for new clusters. Use `id` as `k8s_version_id` when creating a cluster. operationId: listK8sVersions responses: '200': description: Available versions content: application/json: schema: type: object properties: success: type: boolean versions: type: array items: $ref: '#/components/schemas/K8sVersion' '401': $ref: '#/components/responses/Unauthorized' /api/v1/k8s/node-plans: get: tags: - Kubernetes summary: List Kubernetes node plans description: Worker node plans plus HA control-plane and storage-node pricing. Same data as `GET /api/v1/public/pricing/kubernetes`. operationId: listK8sNodePlans responses: '200': description: Node plans and pricing content: application/json: schema: type: object properties: success: type: boolean plans: type: array items: $ref: '#/components/schemas/K8sNodePlan' ha_pricing: $ref: '#/components/schemas/K8sHAPricing' storage_pricing: $ref: '#/components/schemas/K8sStoragePricing' /api/v1/k8s/clusters: get: tags: - Kubernetes summary: List Kubernetes clusters description: | List clusters for the authenticated account, newest first. Pass `X-Project-ID` to scope the list to a single project. When omitted, returns clusters across every project the API key has access to. operationId: listK8sClusters 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: limit in: query description: Page size (max 200) schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of clusters content: application/json: schema: type: object properties: success: type: boolean clusters: type: array items: $ref: '#/components/schemas/K8sCluster' total: type: integer description: Total clusters matching the filter (before pagination) '401': $ref: '#/components/responses/Unauthorized' post: tags: - Kubernetes summary: Create Kubernetes cluster description: | Create a managed Kubernetes cluster. Provisioning is asynchronous — the response returns immediately with `pending` status; the cluster is usable when `ready` is `true` (typically 8–12 minutes). API-created clusters are billed pay-as-you-go per node-hour. Accounts on subscription billing cannot create clusters through the API yet and receive `402` — use the dashboard instead. Retries are safe with an `Idempotency-Key` header: a repeated request with the same key returns the cluster created by the first request instead of provisioning a second one. operationId: createK8sCluster parameters: - $ref: '#/components/parameters/ProjectIDHeader' - name: Idempotency-Key in: header required: false description: Any unique string (max 128 chars). Reuse the same value when retrying a failed or timed-out request. schema: type: string maxLength: 128 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateK8sClusterRequest' responses: '201': description: Cluster accepted for provisioning (status `pending`) content: application/json: schema: type: object properties: success: type: boolean cluster: $ref: '#/components/schemas/K8sCluster' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/InsufficientBalance' /api/v1/k8s/clusters/stats: get: tags: - Kubernetes summary: Get cluster utilization stats description: Batch utilization snapshot for one or more clusters (CPU, memory, pods, node readiness). Values refresh every 60 seconds. operationId: getK8sClusterStats parameters: - $ref: '#/components/parameters/ProjectIDHeader' - name: ids in: query required: false description: Comma-separated cluster IDs. Omit for all account clusters. schema: type: string responses: '200': description: Utilization per cluster content: application/json: schema: type: object properties: success: type: boolean stats: type: array items: $ref: '#/components/schemas/K8sClusterStats' '401': $ref: '#/components/responses/Unauthorized' /api/v1/k8s/clusters/{cluster_id}: get: tags: - Kubernetes summary: Get Kubernetes cluster operationId: getK8sCluster parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Cluster details including node pools content: application/json: schema: type: object properties: success: type: boolean cluster: $ref: '#/components/schemas/K8sCluster' '404': $ref: '#/components/responses/NotFound' patch: tags: - Kubernetes summary: Rename Kubernetes cluster operationId: renameK8sCluster parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: type: object required: [name] properties: name: type: string description: New cluster name (1–63 lowercase letters, digits or hyphens) example: prod-eu responses: '200': description: Cluster renamed content: application/json: schema: type: object properties: success: type: boolean cluster: $ref: '#/components/schemas/K8sCluster' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' delete: tags: - Kubernetes summary: Delete Kubernetes cluster description: | Delete a cluster and all its infrastructure (nodes, networking, reserved IPs, DNS). Deletion is asynchronous. If teardown cannot be completed, the cluster remains visible with status `delete_failed` and nothing is removed — repeat the delete to retry. operationId: deleteK8sCluster parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Deletion started content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' /api/v1/k8s/clusters/{cluster_id}/kubeconfig: get: tags: - Kubernetes summary: Get kubeconfig description: | Download the cluster's admin kubeconfig. Grants full cluster-admin access — store it like a password. operationId: getK8sKubeconfig parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Kubeconfig for kubectl content: application/json: schema: type: object properties: success: type: boolean kubeconfig: type: string description: kubeconfig YAML api_endpoint: type: string example: https://q7z7zg.k8s.raffusercloud.com:6443 '404': $ref: '#/components/responses/NotFound' /api/v1/k8s/clusters/{cluster_id}/ha: post: tags: - Kubernetes summary: Upgrade to HA control plane description: | Promote a single-master cluster to a 3-master highly available control plane. The flat HA fee starts accruing once enabled. Available for pay-as-you-go clusters; subscription clusters upgrade through the dashboard checkout and receive `409` here. operationId: upgradeK8sClusterHA parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: HA upgrade started content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' '409': description: Cluster already HA, or subscription-billed (upgrade via dashboard) content: application/json: schema: $ref: '#/components/schemas/Error' /api/v1/k8s/clusters/{cluster_id}/upgrades: get: tags: - Kubernetes summary: List available version upgrades description: | The Kubernetes versions this cluster can upgrade to right now — patch releases of its current minor and the next minor (Kubernetes upgrades one minor at a time) — plus the cluster's upgrade state, mode and maintenance window. operationId: listK8sClusterUpgrades parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Upgrade state and available targets content: application/json: schema: type: object properties: success: type: boolean current_version: type: string example: v1.33.4+rke2r1 upgrade_status: type: string enum: [idle, scheduled, upgrading, failed] target_version: type: string description: Set while an upgrade is in flight or failed upgrade_mode: type: string enum: [manual, auto_patch, auto_minor] maintenance_day: type: integer description: 0 (Sunday) to 6 (Saturday) maintenance_start: type: integer description: Window start hour (UTC); the window is 4 hours available: type: array items: type: object properties: version_id: type: integer version: type: string example: v1.34+rke2r1 rke2_version: type: string example: v1.34.6+rke2r1 is_minor: type: boolean description: true for a minor hop, false for a patch is_default: type: boolean '404': $ref: '#/components/responses/NotFound' /api/v1/k8s/clusters/{cluster_id}/upgrade: post: tags: - Kubernetes summary: Start a version upgrade description: | Start an in-place Kubernetes upgrade to one of the versions from [List available version upgrades](#tag/Kubernetes/operation/listK8sClusterUpgrades). The upgrade is rolling: control-plane nodes one at a time, then workers drained first (PodDisruptionBudgets honoured). HA clusters upgrade with no API interruption; single-master clusters have a brief interruption and require `confirm_single_master: true`. Progress appears in the cluster events; upgrades are free. operationId: upgradeK8sClusterVersion parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: type: object required: [version_id] properties: version_id: type: integer description: Target from List available version upgrades confirm_single_master: type: boolean description: Required true on non-HA clusters responses: '200': description: Upgrade started content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': description: Invalid target (skew policy, unknown version, downgrade) content: application/json: schema: $ref: '#/components/schemas/Error' '404': $ref: '#/components/responses/NotFound' '412': description: Pre-flight refused (busy pool, unconfirmed single-master) content: application/json: schema: $ref: '#/components/schemas/Error' /api/v1/k8s/clusters/{cluster_id}/maintenance: patch: tags: - Kubernetes summary: Set upgrade mode and maintenance window description: | `manual` (default) — you start upgrades yourself. `auto_patch` — patch releases are applied automatically in the maintenance window (notice email ~72h before). `auto_minor` — minors too, after a stability period. The window is 4 hours from `maintenance_start` (UTC) on `maintenance_day`. operationId: updateK8sClusterMaintenance parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: type: object required: [upgrade_mode] properties: upgrade_mode: type: string enum: [manual, auto_patch, auto_minor] maintenance_day: type: integer minimum: 0 maximum: 6 description: 0 (Sunday) to 6 (Saturday) maintenance_start: type: integer minimum: 0 maximum: 23 description: Window start hour (UTC) responses: '200': description: Settings updated content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': description: Invalid mode or window content: application/json: schema: $ref: '#/components/schemas/Error' '404': $ref: '#/components/responses/NotFound' /api/v1/k8s/clusters/{cluster_id}/pools: get: tags: - Kubernetes summary: List node pools operationId: listK8sNodePools parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Node pools with their nodes content: application/json: schema: type: object properties: success: type: boolean node_pools: type: array items: $ref: '#/components/schemas/K8sNodePool' '404': $ref: '#/components/responses/NotFound' post: tags: - Kubernetes summary: Add node pool description: | Add a node pool. Nodes deploy asynchronously; the pool is `running` once every node has joined. Available for pay-as-you-go clusters; subscription clusters add pools through the dashboard checkout and receive `409` here. operationId: addK8sNodePool parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AddK8sNodePoolRequest' responses: '201': description: Node pool created content: application/json: schema: type: object properties: success: type: boolean node_pool: $ref: '#/components/schemas/K8sNodePool' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '409': description: Subscription-billed cluster — add the pool via the dashboard content: application/json: schema: $ref: '#/components/schemas/Error' /api/v1/k8s/clusters/{cluster_id}/pools/{pool_id}: patch: tags: - Kubernetes summary: Update node pool description: Update a pool's autoscaling bounds, labels and taints. Labels and taints are applied to the pool's Kubernetes nodes within a minute. operationId: updateK8sNodePool parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/K8sPoolIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateK8sNodePoolRequest' responses: '200': description: Updated node pool content: application/json: schema: type: object properties: success: type: boolean node_pool: $ref: '#/components/schemas/K8sNodePool' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' delete: tags: - Kubernetes summary: Delete node pool description: | Remove a pool and all its nodes. Each node is cordoned and drained (PodDisruptionBudgets are honoured) before its VM is terminated, one at a time; the operation runs asynchronously and stops — with the pool intact — if a node cannot be emptied. The last pool of a cluster cannot be deleted, and the cluster keeps at least 2 worker nodes. operationId: deleteK8sNodePool parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/K8sPoolIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Pool deletion started content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /api/v1/k8s/clusters/{cluster_id}/pools/{pool_id}/scale: post: tags: - Kubernetes summary: Scale node pool description: | Set the pool's node count (2–20). Scale-up deploys new nodes; scale-down cordons and drains the newest nodes first — honouring PodDisruptionBudgets, never force-deleting — then removes their VMs. Both run asynchronously; watch the pool's `status` and `status_message`. Scale-up on a subscription-billed cluster goes through the dashboard checkout and receives `409` here; scale-down works for every cluster. operationId: scaleK8sNodePool parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/K8sPoolIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: type: object required: [node_count] properties: node_count: type: integer minimum: 2 maximum: 20 example: 3 responses: '200': description: Scaling started content: application/json: schema: type: object properties: success: type: boolean message: type: string new_node_count: type: integer '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '409': description: Pool busy (already scaling), or subscription-billed scale-up (use the dashboard) content: application/json: schema: $ref: '#/components/schemas/Error' /api/v1/k8s/clusters/{cluster_id}/nodes: get: tags: - Kubernetes summary: List cluster nodes description: All nodes of the cluster (control plane, workers, storage, gateway) merged with live Kubernetes state when reachable. operationId: listK8sClusterNodes parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Cluster nodes content: application/json: schema: type: object properties: success: type: boolean nodes: type: array items: $ref: '#/components/schemas/K8sClusterNode' '404': $ref: '#/components/responses/NotFound' /api/v1/k8s/clusters/{cluster_id}/events: get: tags: - Kubernetes summary: List cluster events description: Lifecycle events (provisioning steps, scaling, autoscaling, warnings), newest first. operationId: listK8sClusterEvents parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' - name: limit in: query schema: type: integer default: 50 responses: '200': description: Cluster events content: application/json: schema: type: object properties: success: type: boolean events: type: array items: $ref: '#/components/schemas/K8sClusterEvent' '404': $ref: '#/components/responses/NotFound' /api/v1/k8s/clusters/{cluster_id}/metrics: get: tags: - Kubernetes summary: Get cluster metrics description: Live utilization (CPU, memory, pods, per-node breakdown) plus a 24-hour control-plane health summary. Values refresh every 60 seconds. operationId: getK8sClusterMetrics parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Cluster metrics content: application/json: schema: type: object properties: success: type: boolean cpu_used_millicores: type: integer cpu_capacity_millicores: type: integer mem_used_bytes: type: integer mem_capacity_bytes: type: integer pod_count: type: integer pod_capacity: type: integer nodes_ready: type: integer nodes_total: type: integer nodes: type: array items: $ref: '#/components/schemas/K8sNodeMetrics' control_plane: $ref: '#/components/schemas/K8sControlPlaneHealth' '404': $ref: '#/components/responses/NotFound' '503': description: Cluster unreachable content: application/json: schema: $ref: '#/components/schemas/Error' /api/v1/k8s/clusters/{cluster_id}/firewall: get: tags: - Kubernetes summary: Get cluster firewall description: The security group protecting the cluster's public gateway, and the account's security groups available to assign. operationId: getK8sClusterFirewall parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Firewall state content: application/json: schema: $ref: '#/components/schemas/K8sFirewallState' '404': $ref: '#/components/responses/NotFound' post: tags: - Kubernetes summary: Assign cluster firewall operationId: assignK8sClusterFirewall parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: type: object required: [security_group_id] properties: security_group_id: type: string format: uuid responses: '200': description: Security group assigned content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' delete: tags: - Kubernetes summary: Reset cluster firewall description: Detach the custom security group and restore the platform baseline. operationId: resetK8sClusterFirewall parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Firewall reset to baseline content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '404': $ref: '#/components/responses/NotFound' /api/v1/k8s/clusters/{cluster_id}/metallb-pool: get: tags: - Kubernetes summary: Get MetalLB address pool operationId: getK8sMetalLBPool parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: MetalLB pool state content: application/json: schema: type: object properties: success: type: boolean pool: $ref: '#/components/schemas/K8sMetalLBPool' '404': $ref: '#/components/responses/NotFound' put: tags: - Kubernetes summary: Resize MetalLB address pool description: Set how many private IPs MetalLB can hand to `LoadBalancer` services. operationId: updateK8sMetalLBPool parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' requestBody: required: true content: application/json: schema: type: object required: [size] properties: size: type: integer example: 16 responses: '200': description: Updated pool content: application/json: schema: type: object properties: success: type: boolean pool: $ref: '#/components/schemas/K8sMetalLBPool' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /api/v1/k8s/clusters/{cluster_id}/tcp-ports: get: tags: - Kubernetes summary: List TCP port forwards description: L4 forwards published on the cluster's public gateway (created when exposing an app over TCP). operationId: listK8sTcpPorts parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' responses: '200': description: Active TCP forwards content: application/json: schema: type: object properties: success: type: boolean forwards: type: array items: $ref: '#/components/schemas/K8sTcpForward' '404': $ref: '#/components/responses/NotFound' /api/v1/k8s/clusters/{cluster_id}/tcp-ports/{port}: delete: tags: - Kubernetes summary: Delete TCP port forward operationId: deleteK8sTcpPort parameters: - $ref: '#/components/parameters/K8sClusterIDPath' - $ref: '#/components/parameters/ProjectIDHeader' - name: port in: path required: true description: Public port of the forward schema: type: integer responses: '200': description: Forward removed content: application/json: schema: $ref: '#/components/schemas/SuccessResponse' '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: FunctionRef: name: function_id in: path required: true description: Function ID, short id (`fn-…`), URL slug, or name schema: type: string DatabaseIDPath: name: database_id in: path required: true description: Database ID (UUID) or short id (the `database_id` field, e.g. `a1b2c3d4`) schema: type: string AppServiceRef: name: service_id in: path required: true description: App service ID (UUID) or short id (the `service_id` field) schema: type: string AppDeploymentIDPath: name: deployment_id in: path required: true description: Deployment ID (UUID) schema: type: string format: uuid AppDomainIDPath: name: domain_id in: path required: true description: Custom domain ID (UUID) schema: type: string format: uuid 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 K8sClusterIDPath: name: cluster_id in: path required: true description: Cluster ID (short id, e.g. `q7z7zg`) schema: type: string K8sPoolIDPath: name: pool_id in: path required: true description: Node pool ID 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. is_primary: type: boolean description: True for the VM's primary public IPv4 interface. A VM has at most one primary IPv4; additional public IPv4s report `false`. example: true 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 on attach — you're already paying for the reserved IP. Use this to add up to 4 public IPv4 addresses to a VM. type: type: string enum: [IPv4, IPv6] description: | IP family to auto-allocate. Used only when `ip_id` is omitted. Defaults to `IPv4`. Available only while the VM has no public IP of that family; the IP is included with the VM at no extra charge and is released back to the pool on detach. 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, windows] 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 DatabasePricingPlan: type: object required: - id - engine - name - vcpu - memory_gib - storage_gib - price_per_hour - price_per_month - region properties: id: type: integer description: Plan ID example: 5 engine: $ref: '#/components/schemas/DatabaseEngine' name: type: string description: Plan name example: 1 vCPU vcpu: type: integer description: Number of vCPU cores example: 1 memory_gib: type: integer description: RAM in GiB example: 1 storage_gib: type: integer description: Included SSD storage in GiB example: 25 price_per_hour: type: number description: Hourly price in USD example: 0.010945 price_per_month: type: number description: Monthly price in USD example: 7.99 region: type: string enum: [us-east] description: Region this plan is available in example: us-east is_free_tier: type: boolean description: | Free plan — one per account, no HA or replicas, fixed storage, pauses after 7 idle days (resume keeps all data). example: false memory_mib: type: integer description: RAM override in MiB for sub-GiB plans (free Valkey = 512). 0 means `memory_gib` applies. example: 0 max_connections: type: integer description: Client connection limit enforced by the engine. 0 means the engine default. example: 100 DatabaseHAPricing: type: object description: High availability pricing for one engine. An HA plan costs `price_per_month * (1 + price_multiplier)`. properties: engine: $ref: '#/components/schemas/DatabaseEngine' price_multiplier: type: number description: Fraction of the plan price added for high availability example: 0.7 DatabaseReplicaPricing: type: object description: Read replica pricing for one engine. properties: engine: $ref: '#/components/schemas/DatabaseEngine' price_multiplier: type: number description: Multiplier applied to the replica base price example: 1.0 price_per_month: type: number description: Monthly price per read replica in USD example: 29.99 DatabaseStoragePricing: type: object description: Extra storage pricing for one engine. properties: engine: $ref: '#/components/schemas/DatabaseEngine' price_per_gb_month: type: number description: Price per GB per month in USD example: 0.12 min_gb: type: integer description: Minimum storage in GB example: 10 max_gb: type: integer description: Maximum storage in GB example: 10000 K8sNodePlan: type: object required: - id - name - vcpu - memory_gib - ssd_gib - price_per_hour - price_per_month - region properties: id: type: integer description: Node plan ID example: 11 name: type: string description: Node plan name example: K8s Starter vcpu: type: integer description: Number of vCPU cores per node example: 1 memory_gib: type: integer description: RAM in GiB per node example: 2 ssd_gib: type: integer description: NVMe root disk in GiB per node (OS, container images, logs, ephemeral storage; persistent volumes come from storage nodes) example: 25 transfer_gib: type: integer description: Monthly data transfer in GiB (0 means unmetered) example: 0 price_per_hour: type: number description: Hourly price per node in USD (pay-as-you-go) example: 0.013875 price_per_month: type: number description: Monthly price per node in USD example: 9.99 total_price: type: number description: Monthly price per node in USD (same as price_per_month) example: 9.99 yearly_price: type: number description: Yearly commitment price in USD (0 if not offered) example: 0 twenty_four_month_price: type: number description: 24-month commitment price in USD (0 if not offered) example: 0 region: type: string enum: [us-east] description: Region this plan is available in example: us-east K8sHAPricing: type: object description: Optional highly available control plane pricing. The standard control plane is free. properties: price_per_hour: type: number description: Hourly price in USD example: 0.0411 price_per_month: type: number description: Monthly price in USD example: 30.00 K8sStoragePricing: type: object description: Per-GB pricing for dedicated NVMe storage nodes. properties: per_gb_month: type: number description: Price per GB per month in USD example: 0.08 per_gb_hour: type: number description: Price per GB per hour in USD example: 0.00010959 min_gb: type: integer description: Minimum storage node size in GB example: 100 max_gb: type: integer description: Maximum storage node size in GB example: 1000 default_gb: type: integer description: Default storage node size in GB example: 100 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 enabled: type: boolean description: Whether the schedule is active. Disabled schedules never run. next_run_at: type: string format: date-time description: Next time this schedule runs (UTC). last_run_at: type: string format: date-time description: Last time this schedule ran. Absent if it has never run. last_run_status: type: string enum: [succeeded, failed, skipped] description: Outcome of the most recent run. Absent if it has never run. consecutive_failures: type: integer description: Consecutive failed runs. Resets to 0 on the next successful run. 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: 2 maximum: 100 description: Number of backups to retain before auto-pruning (2-100) 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: 2 maximum: 100 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.' Function: type: object description: A serverless function. Handlers are standard (ASGI / Web Fetch / net/http / any container listening on $PORT) — nothing Raff-specific. properties: id: type: string format: uuid function_id: type: string description: Short reference id, e.g. `fn-a1b2c3d4` project_id: type: string format: uuid name: type: string description: Display name, unique within the account slug: type: string description: URL label — the name plus a short random suffix (never collides across accounts) description: type: string runtime: type: string enum: [python, node, go, docker] handler_kind: type: string description: "`asgi`, `fetch`, `net_http` or `container`" region: type: string enum: [us-east] memory_mb: type: integer description: "One of: 128, 256, 512, 1024, 2048, 4096" timeout_seconds: type: integer description: Up to 3600, or 86400 with `extended_timeout` extended_timeout: type: boolean min_scale: type: integer description: 0 = scale to zero (idle bills nothing); ≥1 keeps warm instances at 30% of the memory rate max_scale: type: integer source_type: type: string enum: [console, cli, github] repo_full_name: type: string description: GitHub source only repo_branch: type: string repo_root_dir: type: string url: type: string description: "`https://{slug}.fn.raffusercloud.com` — HTTPS with automatic TLS" status: type: string enum: [pending, building, live, paused, error, deleting] status_message: type: string current_revision_number: type: integer created_at: type: string format: date-time updated_at: type: string format: date-time CreateFunctionRequest: type: object required: [name, runtime] properties: name: type: string slug: type: string description: Optional URL label base (a random suffix is always appended) description: type: string runtime: type: string enum: [python, node, go, docker] region: type: string enum: [us-east] memory_mb: type: integer default: 256 timeout_seconds: type: integer default: 300 extended_timeout: type: boolean description: Allow timeouts up to 86400s (24h) min_scale: type: integer default: 0 max_scale: type: integer default: 20 source_type: type: string enum: [console, cli, github] UpdateFunctionRequest: type: object description: All fields optional — only provided fields change. properties: description: type: string memory_mb: type: integer timeout_seconds: type: integer extended_timeout: type: boolean min_scale: type: integer max_scale: type: integer FunctionDeployment: type: object properties: id: type: string format: uuid revision_id: type: string format: uuid description: Set when the deployment succeeds channel: type: string enum: [console, cli, github] status: type: string enum: [queued, building, deploying, ready, failed] commit_sha: type: string error: type: string build_duration_ms: type: integer started_at: type: string format: date-time finished_at: type: string format: date-time created_at: type: string format: date-time CreateFunctionDeploymentRequest: type: object properties: channel: type: string enum: [console, cli] source_blob_key: type: string description: From a completed source upload template: type: string enum: [python, typescript, node, go] description: Deploy a starter template instead of uploaded source files: type: array description: Inline source files (small edits) items: $ref: '#/components/schemas/FunctionSourceFile' message: type: string FunctionRevision: type: object properties: id: type: string format: uuid revision_number: type: integer commit_sha: type: string message: type: string status: type: string enum: [active, superseded, failed] is_current: type: boolean created_at: type: string format: date-time FunctionSourceUpload: type: object properties: upload_url: type: string description: Presigned URL — PUT the zip here (Content-Type application/zip) source_blob_key: type: string expires_in_seconds: type: integer FunctionSourceFile: type: object required: [name, content] properties: name: type: string content: type: string FunctionBuildLogLine: type: object properties: timestamp: type: string step: type: string message: type: string FunctionLogLine: type: object properties: timestamp: type: string format: date-time level: type: string enum: [info, warn, error, debug] message: type: string stream: type: string enum: [stdout, stderr] FunctionMetrics: type: object properties: series: type: array items: type: object properties: name: type: string enum: [invocations, p50_ms, p95_ms, errors] points: type: array items: type: object properties: timestamp: type: string format: date-time value: type: number invocations_total: type: integer error_rate_pct: type: number p50_ms: type: number p95_ms: type: number FunctionInvocation: type: object properties: timestamp: type: string format: date-time method: type: string path: type: string status: type: integer duration_ms: type: number FunctionsUsage: type: object properties: memory_gb_seconds: type: number warm_gb_seconds: type: number cpu_seconds: type: number request_count: type: integer description: Informational — requests are free free_gb_seconds_remaining: type: number free_vcpu_seconds_remaining: type: number estimated_cost_usd: type: number description: Month-to-date, after the free tier ai_conversions: type: integer description: AI Lambda-rewrites used this calendar month period_start: type: string format: date-time period_end: type: string format: date-time as_of: type: string format: date-time description: Metering watermark (near-real-time) FunctionsSpendSettings: type: object properties: enabled: type: boolean monthly_cap_usd: type: number cap_action: type: string enum: [pause] paused_by_cap: type: boolean FunctionTrigger: type: object properties: id: type: string format: uuid trigger_type: type: string enum: [cron, once, s3] enabled: type: boolean cron_expression: type: string timezone: type: string run_at: type: string format: date-time next_run_at: type: string format: date-time payload_json: type: string bucket_id: type: string format: uuid description: Source bucket (`s3` type) s3_events: type: array items: type: string description: "Subscribed events, e.g. s3:ObjectCreated:*, s3:ObjectRemoved:*" key_prefix: type: string key_suffix: type: string last_run_at: type: string format: date-time last_status: type: string enum: [ok, failed, ''] created_at: type: string format: date-time CreateFunctionTriggerRequest: type: object required: [trigger_type] properties: trigger_type: type: string enum: [cron, once, s3] cron_expression: type: string description: 5-field cron (required for `cron`) timezone: type: string description: IANA name, default UTC run_at: type: string format: date-time description: Required for `once` payload_json: type: string description: JSON merged into the CloudEvent data (cron/once) bucket_id: type: string format: uuid description: Required for `s3` — an Object Storage bucket you own s3_events: type: array items: type: string description: "Default s3:ObjectCreated:*; also s3:ObjectRemoved:*" key_prefix: type: string description: Only objects whose key starts with this fire the trigger key_suffix: type: string description: Only objects whose key ends with this fire the trigger FunctionTriggerRun: type: object properties: id: type: string format: uuid trigger_id: type: string format: uuid scheduled_for: type: string format: date-time status: type: string enum: [dispatching, ok, failed] attempts: type: integer response_status: type: integer duration_ms: type: integer error: type: string finished_at: type: string format: date-time FunctionEnvVar: type: object properties: key: type: string value: type: string description: Empty for secrets unless fetched via the reveal endpoint is_secret: type: boolean is_system: type: boolean description: Managed by the platform (e.g. bindings) — read-only binding_id: type: string format: uuid description: Owning binding for system vars updated_at: type: string format: date-time FunctionBinding: type: object properties: id: type: string format: uuid binding_type: type: string enum: [database, bucket] resource_id: type: string format: uuid resource_name: type: string env_prefix: type: string injected_keys: type: array items: type: string status: type: string enum: [provisioning, active, rotating, error, deleting] status_message: type: string created_at: type: string format: date-time CreateFunctionBindingRequest: type: object required: [binding_type, resource_id] properties: binding_type: type: string enum: [database, bucket] resource_id: type: string format: uuid description: A managed database or Object Storage bucket you own env_prefix: type: string description: Env key prefix (default DATABASE / STORAGE) ConvertLambdaRequest: type: object required: [lambda_runtime, files] properties: lambda_runtime: type: string description: "AWS runtime id, e.g. `python3.12`, `nodejs20.x`, `go1.x`" files: type: array items: $ref: '#/components/schemas/FunctionSourceFile' handler: type: string description: "Lambda handler, e.g. `lambda_function.handler`" memory_mb: type: integer timeout_seconds: type: integer env_vars: type: object additionalProperties: type: string trigger_config_json: type: string description: Optional EventBridge/S3/FunctionURL config to map mode: type: string enum: [rewrite, adapter] default: rewrite description: "`rewrite` = AI portable-handler rewrite; `adapter` = run unchanged via a static shim (free)" ConvertLambdaResult: type: object properties: runtime: type: string enum: [python, node, go] converted_files: type: array items: $ref: '#/components/schemas/FunctionSourceFile' raff_toml: type: string description: Mapped configuration (review before deploying) proposed_triggers: type: array items: type: string description: Ready-made trigger payloads mapped from EventBridge schedules warnings: type: array items: type: string description: Every guess or behavior difference — review before deploying out_of_scope: type: array items: type: string description: Items that are not migrated, with alternatives free_remaining_month: type: integer conversion_price: type: number charged: type: boolean FunctionRuntimeInfo: type: object properties: id: type: string enum: [python, node, go, docker] label: type: string handler_kind: type: string signature: type: string available: type: boolean detect_hint: type: string FunctionsPricing: type: object properties: name: type: string region: type: string price_per_gb_second: type: number description: Memory, wall-clock price_per_vcpu_second: type: number description: Active CPU actually consumed warm_rate_multiplier: type: number description: Warm instances (min_scale) bill at this fraction of the memory rate free_gb_seconds: type: integer description: Monthly account-wide free tier free_vcpu_seconds: type: integer price_per_ai_conversion: type: number description: Lambda import AI rewrite, beyond the free allowance free_ai_conversions: type: integer description: Per account per calendar month AppService: type: object description: An app service on the Raff PaaS — a web service, private service, worker, cron job, or one-off job. properties: id: type: string format: uuid service_id: type: string description: Short reference id used in URLs project_id: type: string format: uuid name: type: string description: Display name, unique within the project slug: type: string description: URL label — the name plus a short random suffix description: type: string service_type: type: string enum: [web, private, worker, cron, job] source_type: type: string enum: [git, image, template, compose] builder: type: string enum: [buildpacks, dockerfile, image] repo_full_name: type: string description: GitHub source only (owner/name) repo_branch: type: string repo_root_dir: type: string dockerfile_path: type: string image_ref: type: string description: Prebuilt image source only start_command: type: string tier_id: type: integer description: Pricing tier ID (see `GET /api/v1/apps/tiers`) tier_name: type: string replicas: type: integer autoscaling_enabled: type: boolean min_replicas: type: integer max_replicas: type: integer autoscaling_metric: type: string autoscaling_target: type: integer scale_to_zero: type: boolean http_port: type: integer health_check_path: type: string cron_schedule: type: string description: Cron services only cron_timezone: type: string region: type: string url: type: string description: Public HTTPS endpoint (web services; automatic TLS) internal_host: type: string description: In-cluster hostname (private services) status: type: string enum: [pending, building, deploying, live, paused, suspended, error, deleting, deleted] status_message: type: string paused_by_cap: type: boolean description: True when a spend cap paused the service billing_type: type: string enum: [payg, subscription] current_deployment_id: type: string format: uuid created_at: type: string format: date-time updated_at: type: string format: date-time CreateAppServiceRequest: type: object required: [name, service_type] properties: name: type: string description: type: string service_type: type: string enum: [web, private, worker, cron, job] source_type: type: string enum: [git, image, template, compose] builder: type: string enum: [buildpacks, dockerfile, image] image_ref: type: string description: Prebuilt image reference (for source_type `image`) repo_full_name: type: string description: GitHub repo (owner/name) for source_type `git` repo_branch: type: string repo_root_dir: type: string dockerfile_path: type: string start_command: type: string tier_id: type: integer replicas: type: integer autoscaling_enabled: type: boolean min_replicas: type: integer max_replicas: type: integer scale_to_zero: type: boolean http_port: type: integer health_check_path: type: string cron_schedule: type: string cron_timezone: type: string region: type: string deploy_now: type: boolean description: Queue a deployment immediately (prebuilt-image services) ScaleAppServiceRequest: type: object properties: replicas: type: integer autoscaling_set: type: boolean description: Set true to apply the autoscaling fields autoscaling_enabled: type: boolean min_replicas: type: integer max_replicas: type: integer autoscaling_metric: type: string autoscaling_target: type: integer scale_to_zero: type: boolean AppTier: type: object description: One Apps pricing tier — compute size and price. properties: id: type: integer name: type: string vcpu: type: number memory_mib: type: integer ephemeral_gib: type: integer price_per_hour: type: number price_per_month: type: number yearly_price: type: number region: type: string AppDeployment: type: object properties: id: type: string format: uuid service_id: type: string format: uuid deployment_number: type: integer channel: type: string enum: [console, git_push, cli, compose, rollback] status: type: string enum: [queued, building, deploying, live, superseded, failed, canceled] rollback_of: type: string format: uuid description: Set when this deployment is a rollback image_digest: type: string commit_sha: type: string commit_message: type: string error: type: string build_duration_ms: type: integer started_at: type: string format: date-time finished_at: type: string format: date-time created_at: type: string format: date-time CreateAppDeploymentRequest: type: object description: Provide a source_blob_key or an image_ref. Git-connected services deploy their branch head when neither is given. properties: channel: type: string enum: [console, git_push, cli, compose] source_blob_key: type: string description: Blob key from a completed source upload image_ref: type: string description: Prebuilt image reference AppEnvVar: type: object properties: key: type: string value: type: string description: Empty for secrets unless fetched via the reveal endpoint is_secret: type: boolean is_system: type: boolean description: Managed by the platform (e.g. bindings) — read-only AppCustomDomain: type: object properties: id: type: string format: uuid service_id: type: string format: uuid domain: type: string status: type: string enum: [pending_dns, verifying, dns_verified, issuing_cert, active, error] status_message: type: string cname_target: type: string description: CNAME record to create at your DNS provider verification_txt_name: type: string description: TXT record name for domain ownership verification verification_txt_value: type: string created_at: type: string format: date-time last_checked_at: type: string format: date-time AppJobRun: type: object properties: id: type: string format: uuid service_id: type: string format: uuid deployment_id: type: string format: uuid trigger: type: string enum: [schedule, manual] status: type: string exit_code: type: integer error: type: string scheduled_for: type: string format: date-time started_at: type: string format: date-time finished_at: type: string format: date-time AppLogLine: type: object properties: timestamp: type: string format: date-time level: type: string message: type: string stream: type: string description: "`stdout` or `stderr`" component: type: string AppBuildLogLine: type: object properties: timestamp: type: string format: date-time message: type: string component: type: string stream: type: string AppMetricSeries: type: object properties: name: type: string values: type: array items: type: number timestamps: type: array items: type: string format: date-time AppUsage: type: object properties: success: type: boolean services: type: array items: type: object properties: service_id: type: string service_name: type: string tier_id: type: integer replica_hours: type: number estimated_cost_usd: type: number total_estimated_cost_usd: type: number period_start: type: string format: date-time period_end: type: string format: date-time AppSpendSettings: type: object properties: enabled: type: boolean monthly_cap_usd: type: number cap_action: type: string enum: [pause] paused_by_cap: type: boolean Database: type: object description: | A managed database instance. The private endpoint `{database_id}.db.raffusercloud.com` lives inside the attached VPC; public access is optional. TLS is always required. properties: id: type: string format: uuid description: Unique database identifier database_id: type: string description: Short reference id used in URLs and DNS hostnames, e.g. `a1b2c3d4` project_id: type: string format: uuid description: Project this database belongs to name: type: string description: Display name example: orders-db engine: $ref: '#/components/schemas/DatabaseEngine' engine_version: type: string description: Engine major version example: "16" status: type: string enum: [pending, deploying, running, warning, suspended, failed, deleting, deleted] description: | Lifecycle status. Creation: `pending` → `deploying` → `running`. `warning` means running with a degraded component; `suspended` means a free-tier database paused for idleness (resume any time — data is kept); `failed` means provisioning or a scale operation failed. status_message: type: string description: Human-readable detail for `warning` and `failed` states region: type: string enum: [us-east] description: Data center region example: us-east plan_id: type: integer description: Pricing plan ID (see `GET /api/v1/databases/plans`) example: 5 storage_gb: type: integer description: Provisioned storage in GB. Storage above the plan's included allotment bills at $0.12/GB/month. example: 25 ha_enabled: type: boolean description: | High availability — a standby that takes over automatically on failure (Sentinel failover for Valkey). Adds 70% of the plan price. replica_count: type: integer description: Number of read replicas (PostgreSQL only; always 0 for Valkey) example: 0 vpc_id: type: string format: uuid description: VPC the private endpoint is attached to (empty if not yet connected) public_access: type: boolean description: Whether public access is enabled connection_host: type: string description: Private endpoint hostname, resolvable inside the attached VPC example: a1b2c3d4.db.raffusercloud.com connection_port: type: integer description: "Private endpoint port. PostgreSQL: 6432 pooled (PgBouncer, transaction mode), 5432 direct. Valkey: 6379 (TLS)." example: 6432 billing_type: type: string enum: [payg, subscription] description: Billing type for this database example: payg subscription_id: type: string format: uuid description: Subscription ID if billing_type is subscription price_per_hour: type: number description: Hourly billing rate in USD example: 0.010945 monthly_price: type: number description: Monthly price in USD example: 7.99 public_port: type: integer description: | Public port from the reserved range 25060–26060; `0` when public access is off. For PostgreSQL, `public_port` is the pooled endpoint and `public_port + 1` is the direct endpoint. example: 25060 public_dns_hostname: type: string description: Public endpoint hostname (empty when public access is off) example: a1b2c3d4.public.db.raffusercloud.com public_allowlist: type: array items: type: string description: Source CIDRs allowed on the public endpoint. Empty = allow all sources. example: ["203.0.113.0/24"] created_at: type: string format: date-time updated_at: type: string format: date-time CreateDatabaseRequest: type: object required: - name - plan_id properties: name: type: string description: Display name, unique within the account example: orders-db engine: $ref: '#/components/schemas/DatabaseEngine' engine_version: type: string description: Engine major version from the engine catalog. Omit for the default version. example: "16" plan_id: type: integer description: Pricing plan ID from `GET /api/v1/databases/plans` example: 5 storage_gb: type: integer description: Storage in GB. Omit to use the plan's included allotment; storage above it bills at $0.12/GB/month. ha_enabled: type: boolean default: false description: Enable high availability (+70% of the plan price) replica_count: type: integer default: 0 description: Read replicas (PostgreSQL only; Valkey rejects a count above 0) region: type: string enum: [us-east] default: us-east description: Data center region vpc_id: type: string format: uuid description: VPC to place the private endpoint in. Omit to create without VPC connectivity and attach one later via `POST /api/v1/databases/{database_id}/vpc`. public_access: type: boolean default: false description: Enable public access at create time engine_config: type: object additionalProperties: type: string description: Engine configuration overrides (allowlisted keys only) UpdateDatabaseRequest: type: object required: - name properties: name: type: string description: New display name example: orders-db-v2 ScaleDatabaseRequest: type: object properties: plan_id: type: integer description: New plan ID (same engine). Omit or `0` to keep the current plan. storage_gb: type: integer description: New storage size in GB. Grows only — never below the plan's included allotment. Omit or `0` to keep the current size. set_ha: type: boolean default: false description: Set to `true` to apply the `has_ha` value. When `false` or omitted, high availability is unchanged. has_ha: type: boolean description: Desired high availability state. Only applied when `set_ha` is `true`. replica_count: type: integer description: Desired read replica count. Always applied — pass your current count when not changing it. PostgreSQL only. ConnectDatabaseVPCRequest: type: object required: - vpc_id properties: vpc_id: type: string format: uuid description: VPC to attach the database's private endpoint to DatabasePublicAccessRequest: type: object required: - enabled properties: enabled: type: boolean description: Enable or disable public access allowlist: type: array items: type: string description: Source CIDRs allowed to connect. Omit to keep the stored list; pass `[]` to allow all sources. example: ["203.0.113.0/24"] SetDatabaseExtensionRequest: type: object required: - name - enabled properties: name: type: string description: Extension name from the allowlist (see `GET /api/v1/databases/{database_id}/extensions`) example: pgvector enabled: type: boolean description: "`true` to install, `false` to remove" RestoreDatabaseRequest: type: object required: - clone_to_new properties: clone_to_new: type: boolean description: Must be `true` — restores always create a new database; in-place restore is not supported backup_id: type: string format: uuid description: Restore from this completed backup. Omit to use `pitr_timestamp` or the latest state. pitr_timestamp: type: string format: date-time description: Point-in-time restore target (RFC 3339). PostgreSQL only — Valkey restores from snapshots via `backup_id`. new_name: type: string description: Name for the new database. Defaults to `{source-name}-restored`; auto-suffixed (`-2`, `-3`, …) if taken. ha_enabled: type: boolean default: false description: Enable high availability on the new database (+70% of the plan price). Never inherited from the source. DatabaseConnection: type: object properties: host: type: string description: Endpoint hostname example: a1b2c3d4.db.raffusercloud.com port: type: integer description: Endpoint port example: 6432 username: type: string description: Database user password: type: string description: Password — empty unless the request was made with `reveal=true` database_name: type: string description: Default database name connection_uri: type: string description: Ready-to-use connection URI (includes the password only when revealed) ca_cert: type: string description: >- PEM CA bundle your client must trust, for engines whose certificates come from a private CA (currently Kafka). Empty for other engines. DatabaseMetrics: type: object description: Latest usage snapshot, refreshed about every 60 seconds. Zero values mean not measured yet. properties: cpu_used_millicores: type: integer description: CPU in use, millicores cpu_capacity_millicores: type: integer description: CPU capacity, millicores mem_used_bytes: type: integer description: Memory in use, bytes mem_capacity_bytes: type: integer description: Memory capacity, bytes storage_used_bytes: type: integer description: Storage in use, bytes storage_capacity_bytes: type: integer description: Storage capacity, bytes connections_active: type: integer description: Active client connections connections_max: type: integer description: Connection limit replication_lag_bytes: type: integer description: Standby/replica lag in bytes (0 without HA or replicas) archive_lag_bytes: type: integer description: WAL archiving lag in bytes (PostgreSQL) metrics_updated_at: type: string format: date-time description: When this snapshot was taken DatabaseMetricsPoint: type: object description: One bucketed point in the metrics history. properties: recorded_at: type: string format: date-time cpu_used_millicores: type: integer cpu_capacity_millicores: type: integer mem_used_bytes: type: integer mem_capacity_bytes: type: integer storage_used_bytes: type: integer storage_capacity_bytes: type: integer connections_active: type: integer connections_max: type: integer replication_lag_bytes: type: integer archive_lag_bytes: type: integer DatabaseEngine: type: string enum: [postgres, mysql, valkey, clickhouse, kafka] description: >- Database engine identifier. Defaults to `postgres` on create; the live catalog (including availability and versions) is `GET /api/v1/databases/engines`. example: postgres DatabaseEngineInfo: type: object properties: engine: $ref: '#/components/schemas/DatabaseEngine' display_name: type: string example: PostgreSQL available: type: boolean description: Whether this engine can be created. MySQL is listed but not yet available. versions: type: array items: type: string description: Supported major versions example: ["16", "15"] DatabaseExtension: type: object description: A PostgreSQL extension from the allowlist. properties: name: type: string description: Extension name, used with `POST /api/v1/databases/{database_id}/extensions` example: pgvector display_name: type: string description: type: string installed: type: boolean description: Whether the extension is currently installed version: type: string description: Installed version (empty when not installed) DatabaseSlowQuery: type: object description: | One slow-query entry. PostgreSQL rows are `pg_stat_statements` aggregates per normalized query; Valkey rows are individual slow commands from `SLOWLOG` (`calls` is 1, `rows` is not applicable). properties: query: type: string description: Normalized query text (PostgreSQL) or the slow command (Valkey) calls: type: integer description: Number of executions aggregated into this entry mean_ms: type: number description: Mean execution time in milliseconds total_ms: type: number description: Total execution time in milliseconds rows: type: integer description: Total rows returned or affected (PostgreSQL) DatabaseBackup: type: object properties: id: type: string format: uuid description: Backup identifier — use as `backup_id` when restoring database_id: type: string format: uuid description: Database this backup belongs to backup_type: type: string enum: [base, on_demand] description: "`base` = nightly automatic backup; `on_demand` = manually requested" status: type: string enum: [running, completed, failed] description: Only `completed` backups are restorable size_bytes: type: integer description: Backup size in bytes (0 while running) s3_path: type: string description: Object storage location of the backup artifact backup_timestamp: type: string format: date-time description: Consistency point of the backup nullable: true created_at: type: string format: date-time completed_at: type: string format: date-time nullable: true expires_at: type: string format: date-time description: When the backup leaves retention nullable: true K8sVersion: type: object properties: id: type: integer example: 1 version: type: string example: v1.33+rke2r1 display_name: type: string example: Kubernetes 1.33 is_default: type: boolean K8sCluster: type: object required: [id, cluster_id, name, status, ready] properties: id: type: string format: uuid description: Internal UUID cluster_id: type: string description: Short cluster ID used in URLs and the API endpoint hostname example: q7z7zg name: type: string example: prod status: type: string enum: [pending, deploying, running, warning, failed, deleting, delete_failed, deleted] description: | - `pending` / `deploying` — provisioning - `running` — control plane up - `warning` — degraded (see `status_message`) - `failed` — provisioning failed; infrastructure was torn down (see `status_message`) - `deleting` — teardown in progress - `delete_failed` — teardown incomplete; nothing removed — retry the delete status_message: type: string description: Human-readable detail for warning/failed/delete_failed ready: type: boolean description: '`true` when the cluster is running and no node pool is mid-operation — safe to use without interpreting `status`' dns_domain: type: string example: q7z7zg.k8s.raffusercloud.com api_endpoint: type: string example: https://q7z7zg.k8s.raffusercloud.com:6443 k8s_version: type: string example: v1.33.4+rke2r1 k8s_version_id: type: integer region: type: string enum: [us-east] master_count: type: integer description: 1, or 3 with an HA control plane worker_count: type: integer description: Total worker nodes across all pools ha_enabled: type: boolean firewall_enabled: type: boolean longhorn_enabled: type: boolean description: Dedicated storage nodes with Longhorn block storage traefik_enabled: type: boolean metallb_enabled: type: boolean billing_type: type: string enum: [payg, subscription] project_id: type: string format: uuid price_per_hour: type: number format: double description: Current hourly price (workers + storage + HA fee; control plane is free) price_per_month: type: number format: double vpc_id: type: string description: Dedicated VPC the cluster runs in private_vip: type: string description: Cluster gateway IP inside the VPC example: 10.1.0.254 node_pools: type: array items: $ref: '#/components/schemas/K8sNodePool' created_at: type: string format: date-time updated_at: type: string format: date-time CreateK8sClusterRequest: type: object required: [name, node_pools] properties: name: type: string description: 1–63 lowercase letters, digits or hyphens; unique within the account example: prod k8s_version_id: type: integer description: From [List Kubernetes versions](#tag/Kubernetes/operation/listK8sVersions). Omit for the default version. example: 1 node_pools: type: array minItems: 1 description: At least one pool with at least 2 nodes in total items: $ref: '#/components/schemas/K8sNodePoolInput' ha_enabled: type: boolean description: 3-master highly available control plane (flat monthly fee) default: false firewall_enabled: type: boolean description: Attach the platform baseline firewall to the public gateway default: true firewall_security_group_id: type: string format: uuid description: Use one of your security groups instead of the baseline storage_node_count: type: integer enum: [0, 2, 3] description: Dedicated Longhorn storage nodes (0 disables in-cluster block storage) default: 0 storage_node_disk_gb: type: integer description: Data disk per storage node; required when `storage_node_count` > 0. Bounds from [List Kubernetes node plans](#tag/Kubernetes/operation/listK8sNodePlans) `storage_pricing`. example: 100 traefik_enabled: type: boolean description: Install the Traefik ingress controller default: false metallb_enabled: type: boolean description: Install MetalLB for `LoadBalancer` services default: false cluster_cidr: type: string description: Pod network override (private IPv4, /12–/24) default: 10.42.0.0/16 service_cidr: type: string description: Service network override (private IPv4, /12–/24) default: 10.43.0.0/16 region: type: string enum: [us-east] default: us-east K8sNodePoolInput: type: object required: [name, node_count, plan_id] properties: name: type: string description: 1–63 lowercase letters, digits or hyphens; unique within the cluster example: default-pool node_count: type: integer minimum: 2 maximum: 20 example: 3 plan_id: type: integer description: Worker plan from [List Kubernetes node plans](#tag/Kubernetes/operation/listK8sNodePlans) example: 11 min_nodes: type: integer description: Autoscaler floor max_nodes: type: integer description: Autoscaler ceiling autoscale_enabled: type: boolean default: false labels: type: string description: 'JSON object of Kubernetes labels applied to the pool''s nodes, e.g. `{"workload":"web"}`' taints: type: string description: 'JSON array of taints, e.g. `[{"key":"gpu","value":"true","effect":"NoSchedule"}]`' AddK8sNodePoolRequest: type: object required: [name, node_count] properties: name: type: string example: pool-2 node_count: type: integer minimum: 2 maximum: 20 plan_id: type: integer description: Omit to inherit the cluster's worker plan. All pools share it — a differing plan is rejected. min_nodes: type: integer max_nodes: type: integer autoscale_enabled: type: boolean labels: type: string taints: type: string UpdateK8sNodePoolRequest: type: object properties: min_nodes: type: integer max_nodes: type: integer autoscale_enabled: type: boolean labels: type: string taints: type: string K8sNodePool: type: object required: [id, name, node_count, status] properties: id: type: string format: uuid cluster_id: type: string format: uuid name: type: string node_count: type: integer description: Target node count actual_node_count: type: integer description: Nodes that currently exist (differs from `node_count` while scaling) min_nodes: type: integer max_nodes: type: integer autoscale_enabled: type: boolean plan_id: type: integer cpu: type: integer ram: type: integer description: GB disk: type: integer description: GB labels: type: string taints: type: string status: type: string enum: [pending, provisioning, running, scaling, failed, deleting, deleted] description: '`scaling`/`deleting` are asynchronous — watch `status_message` for the outcome (e.g. a drain blocked by a PodDisruptionBudget)' status_message: type: string nodes: type: array items: $ref: '#/components/schemas/K8sNode' created_at: type: string format: date-time updated_at: type: string format: date-time K8sNode: type: object properties: id: type: string format: uuid pool_id: type: string format: uuid cluster_id: type: string format: uuid name: type: string role: type: string enum: [vnf, master, worker, storage] description: '`vnf` is the cluster''s network gateway' status: type: string enum: [pending, provisioning, running, not_ready, cordoned, failed, deleting, deleted] description: '`running` means the kubelet reports Ready; `cordoned` = unschedulable (drain or manual cordon); `not_ready` = joined but failing health checks' cpu: type: integer ram: type: integer description: GB disk: type: integer description: GB ip_address: type: string price_per_hour: type: number format: double created_at: type: string format: date-time updated_at: type: string format: date-time K8sClusterNode: allOf: - $ref: '#/components/schemas/K8sNode' - type: object properties: has_live_data: type: boolean description: Live Kubernetes state was reachable for this node live_name: type: string description: Kubernetes node name ready: type: boolean kubelet_version: type: string os_image: type: string cpu_used_millicores: type: integer cpu_capacity_millicores: type: integer mem_used_bytes: type: integer mem_capacity_bytes: type: integer K8sNodeMetrics: type: object properties: name: type: string description: Kubernetes node name internal_ip: type: string roles: type: string example: control-plane,etcd ready: type: boolean cpu_used_millicores: type: integer cpu_capacity_millicores: type: integer mem_used_bytes: type: integer mem_capacity_bytes: type: integer K8sControlPlaneHealth: type: object description: 24-hour control-plane health summary, sampled every minute. Absent until the first sample after the cluster starts running. properties: sampled_at: type: string format: date-time etcd_p99_ms: type: number format: double description: 99th-percentile etcd request latency over the last minute (healthy clusters sit under 15 ms; sustained values above 100 ms degrade the control plane) etcd_p99_ms_max_24h: type: number format: double apiserver_5xx_24h: type: integer restarts_apiserver_24h: type: integer restarts_etcd_24h: type: integer restarts_scheduler_24h: type: integer restarts_controller_24h: type: integer restarts_cilium_op_24h: type: integer leader_changes_24h: type: integer description: Scheduler + controller-manager restarts — lost leader leases not_ready_minutes_24h: type: integer nodes_not_ready: type: integer K8sClusterStats: type: object properties: cluster_id: type: string description: Short cluster ID has_data: type: boolean cpu_used_millicores: type: integer cpu_capacity_millicores: type: integer mem_used_bytes: type: integer mem_capacity_bytes: type: integer pod_count: type: integer nodes_ready: type: integer nodes_total: type: integer metrics_updated_at: type: string format: date-time K8sClusterEvent: type: object properties: id: type: string format: uuid event_type: type: string example: scale_completed message: type: string example: Node pool default-pool scaled from 2 to 3 nodes progress: type: integer description: 0–100 during provisioning created_at: type: string format: date-time K8sFirewallState: type: object properties: success: type: boolean enabled: type: boolean default_name: type: string description: Platform baseline security group example: raff-default-secure assigned_sg_id: type: string format: uuid description: Custom security group currently assigned (absent when on the baseline) assigned_name: type: string available: type: array description: Your security groups that can be assigned items: type: object properties: id: type: string format: uuid name: type: string inbound_rules: type: integer outbound_rules: type: integer K8sMetalLBPool: type: object properties: enabled: type: boolean found: type: boolean description: The address pool exists in the cluster addresses: type: string example: 10.1.0.238-10.1.0.253 size: type: integer max_size: type: integer K8sTcpForward: type: object properties: index: type: integer public_port: type: integer protocol: type: string example: tcp backend_ip: type: string backend_port: type: integer endpoint: type: string description: Public address of the forward example: 23.26.4.133:5432 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