openapi: "3.1.0" info: title: Two API version: "0.1.0" description: REST API for managing VPCs and Subnets in the Two orchestrator. servers: - url: http://localhost:8080 description: Local development server paths: # ── VPC ──────────────────────────────────────────────────────────────────── /vpcs: get: summary: List all VPCs operationId: listVPCs responses: "200": description: List of VPCs content: application/json: schema: type: array items: $ref: "#/components/schemas/VPC" "500": $ref: "#/components/responses/InternalError" post: summary: Create a VPC operationId: createVPC requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/VPCCreateRequest" responses: "202": description: VPC creation accepted content: application/json: schema: $ref: "#/components/schemas/VPC" "400": description: Missing required field or invalid request body content: application/json: schema: $ref: "#/components/schemas/Error" "409": description: VPC already exists content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" /vpcs/{name}: parameters: - $ref: "#/components/parameters/ResourceName" get: summary: Get VPC status and info operationId: getVPC responses: "200": description: VPC found content: application/json: schema: $ref: "#/components/schemas/VPC" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" delete: summary: Delete a VPC operationId: deleteVPC responses: "202": description: VPC deletion accepted content: application/json: schema: $ref: "#/components/schemas/VPC" "404": $ref: "#/components/responses/NotFound" "409": description: VPC not in a deletable state content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" # ── VM ───────────────────────────────────────────────────────────────────── /vms: get: summary: List all VMs operationId: listVMs responses: "200": description: List of VMs content: application/json: schema: type: array items: $ref: "#/components/schemas/VM" "500": $ref: "#/components/responses/InternalError" post: summary: Start a VM operationId: startVM requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/VMCreateRequest" responses: "202": description: VM start accepted content: application/json: schema: $ref: "#/components/schemas/VM" "400": description: Missing required field or invalid request body content: application/json: schema: $ref: "#/components/schemas/Error" "409": description: VM already exists content: application/json: schema: $ref: "#/components/schemas/Error" "422": description: Subnet not found or not in created state content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" /vms/{name}: parameters: - $ref: "#/components/parameters/ResourceName" get: summary: Get VM status and info operationId: getVM responses: "200": description: VM found content: application/json: schema: $ref: "#/components/schemas/VM" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" delete: summary: Stop a VM operationId: stopVM responses: "202": description: VM stop accepted content: application/json: schema: $ref: "#/components/schemas/VM" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" # ── Subnet ───────────────────────────────────────────────────────────────── /subnets: get: summary: List all subnets operationId: listSubnets responses: "200": description: List of subnets content: application/json: schema: type: array items: $ref: "#/components/schemas/Subnet" "500": $ref: "#/components/responses/InternalError" post: summary: Create a subnet operationId: createSubnet requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SubnetCreateRequest" responses: "202": description: Subnet creation accepted content: application/json: schema: $ref: "#/components/schemas/Subnet" "400": description: Missing required field or unknown iface_type content: application/json: schema: $ref: "#/components/schemas/Error" "409": description: Subnet already exists content: application/json: schema: $ref: "#/components/schemas/Error" "422": description: Parent VPC does not exist or is not ready content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" /subnets/{name}: parameters: - $ref: "#/components/parameters/ResourceName" get: summary: Get subnet status and info operationId: getSubnet responses: "200": description: Subnet found content: application/json: schema: $ref: "#/components/schemas/Subnet" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" delete: summary: Delete a subnet operationId: deleteSubnet responses: "202": description: Subnet deletion accepted content: application/json: schema: $ref: "#/components/schemas/Subnet" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" # ── Components ────────────────────────────────────────────────────────────── components: parameters: ResourceName: name: name in: path required: true schema: type: string description: Resource name schemas: VPCCreateRequest: type: object required: [name, cidr] properties: name: type: string description: Unique name for the VPC, must follow the format vp-[id] pattern: '^vp-.+' example: vp-00001 cidr: type: string description: CIDR block for the entire VPC address space example: "10.0.0.0/16" VPC: type: object properties: name: type: string example: vp-00001 state: type: string enum: [creating, created, deleting, deleted] example: created cidr: type: string example: "10.0.0.0/16" SubnetCreateRequest: type: object required: [name, vpc, interface_ip, cidr] properties: name: type: string description: Unique name for the subnet example: sn-00001 vpc: type: string description: Parent VPC name example: vpc1 mode: type: string description: > Subnet mode. "vxlan" (default): creates a VXLAN tunnel and a host bridge. "bridge": attaches directly to an existing bridge resolved from iface_type in the agent config. "vlan" is reserved for future use. enum: [vxlan, bridge] default: vxlan example: vxlan vxlan_id: type: integer description: VXLAN VNI identifier. Required when mode is "vxlan", ignored otherwise. example: 100 iface_type: type: string description: Interface type key defined in the agent config (e.g. vms, internet, admin). Falls back to default_interface if omitted or unknown. example: vms interface_ip: type: string format: ipv4 description: Gateway IP for the subnet example: "10.10.10.1" cidr: type: string description: Subnet CIDR block example: "10.10.10.0/24" default_route: type: boolean description: > If true, advertise a default route via DHCP. For vxlan mode the gateway is the interface IP. For bridge mode the gateway is read from the host routing table. default: false Subnet: type: object properties: name: type: string example: sn-00001 state: type: string enum: [creating, created, deleting, deleted] example: created vpc: type: string example: vpc1 mode: type: string enum: [vxlan, bridge] example: vxlan vxlan_id: type: integer description: VXLAN VNI. Present only when mode is "vxlan". example: 100 local_iface: type: string description: Resolved interface name from agent config example: br-000000 interface_ip: type: string example: "10.10.10.1" cidr: type: string example: "10.10.10.0/24" default_route: type: boolean example: false VMCreateRequest: type: object required: [name, metadata_port, interfaces, storage] properties: name: type: string example: vm-00001 metadata_port: type: string example: "80" memory: type: integer description: Memory in MB (default 512) example: 1024 cpus: type: integer description: Number of vCPUs (default 1) example: 2 password: type: string sshkey: type: string example: "ssh-ed25519 AAAA..." interfaces: type: array minItems: 1 items: $ref: "#/components/schemas/VMInterface" storage: type: array minItems: 1 items: $ref: "#/components/schemas/VMStorage" VMInterface: type: object required: [subnet, ip, primary] properties: subnet: type: string example: sn-00001 ip: type: string format: ipv4 example: "10.0.0.5" primary: type: boolean example: true VMStorage: type: object required: [path, dev] properties: path: type: string description: Path to the disk image on the host example: /var/lib/two/volumes/abc.qcow2 dev: type: string description: Device name inside the VM pattern: '^[sv]d[a-z]$' example: vda VM: type: object properties: name: type: string example: vm-00001 state: type: string enum: [starting, started, stopping, stopped] example: started metadata_port: type: string example: "80" memory: type: integer example: 1024 cpus: type: integer example: 2 interfaces: type: array items: $ref: "#/components/schemas/VMInterface" storage: type: array items: $ref: "#/components/schemas/VMStorage" Error: type: object properties: error: type: string example: "resource not found" responses: NotFound: description: Resource not found content: application/json: schema: $ref: "#/components/schemas/Error" InternalError: description: Internal server error content: application/json: schema: $ref: "#/components/schemas/Error"