Velocity • Durability

11. Subnets and Attachments

11.1 Subnets

Endpoint Summary

Path Summary Method
/api/v1/subnets List every subnet in the realm GET
/api/v1/subnets/segment-type Get the network segment type new subnets are placed on GET
/api/v1/subnets/segment-type Set the network segment type new subnets are placed on PUT
/api/v1/tenants/{tenant_id}/subnets List a tenant’s subnets GET
/api/v1/tenants/{tenant_id}/subnets Create a new subnet POST
/api/v1/tenants/{tenant_id}/subnets/{subnet_id} Get subnet info GET
/api/v1/tenants/{tenant_id}/subnets/{subnet_id} Update subnet PATCH
/api/v1/tenants/{tenant_id}/subnets/{subnet_id} Delete subnet DELETE
/api/v1/tenants/{tenant_id}/subnets/{subnet_id}/mount-info Get the mount targets on a subnet GET

GET /api/v1/subnets

List every subnet in the realm

Returns every subnet the realm holds, whatever tenant owns it. To read one tenant instead, name it in the path: GET /tenants/{tenant_id}/subnets.

Authentication: required (PasswordLogin or BearerJWT).

Responses

Status Description Schema
200 Successful response containing the list of subnets. SubnetListResponse
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
500 Internal server error. -

Response Body (200, SubnetListResponse)

Field Type Required Default Example Description
subnets array of Subnet object no n/a Array of subnet objects. Empty array if none are found.
total integer yes n/a 0, 1, 12 Total count of subnets returned.

Example

{
   "subnets" : [
      {
         "addr_count" : 12,
         "addr_external" : false,
         "created_at" : "2026-06-29T12:00:00Z",
         "gateway" : "192.168.50.1",
         "id" : "36a8995f-026b-47e1-88bd-7fb9e7239995",
         "ip_range_base" : "192.168.50.0",
         "ip_range_prefix" : 24,
         "mtu" : 1450,
         "name" : "storage-net",
         "range_end" : "192.168.50.200",
         "range_start" : "192.168.50.10",
         "segment_id" : 10,
         "segment_type" : "vlan",
         "tenant_id" : "00000000-0000-0000-0000-000000000001",
         "tunnel_udp_port" : 0,
         "updated_at" : "2026-06-29T12:30:00Z"
      }
   ],
   "total" : 1
}

GET /api/v1/subnets/segment-type

Get the network segment type new subnets are placed on

Returns the realm-wide network segment type new subnets are placed on.

Authentication: required (PasswordLogin or BearerJWT).

Responses

Status Description Schema
200 The network segment type new subnets are placed on. RealmSegmentTypeResponse
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
500 Internal server error. -
503 Service unavailable. A backend dependency the request needs is unreachable. The request was well formed and changed nothing, so the same call is worth retrying once the dependency is back; which one failed is recorded server-side, not in the response. -

Response Body (200, RealmSegmentTypeResponse)

Field Type Required Default Example Description
segment_type SubnetSegmentType (none, vlan, vxlan) yes n/a vxlan, vlan, none The segment type a new subnet is placed on. vlan carries a VLAN ID in segment_id and vxlan a VNI. none is the realm naming no segment at all, which is a state to report rather than an error: a create on such a realm is refused, with a reason.

Example

{
   "segment_type" : "vxlan"
}

PUT /api/v1/subnets/segment-type

Set the network segment type new subnets are placed on

Sets the realm-wide network segment type. Refused while any tenant has a subnet.

Authentication: required (PasswordLogin or BearerJWT).

Request Body (application/json, required)

Schema: RealmSegmentTypeRequest

Set the network segment type new subnets are placed on.

Field Type Required Default Example Description
segment_type one of: vlan, vxlan yes n/a vlan, vxlan The segment type new subnets are placed on.

Example

{
   "segment_type" : "vlan"
}

Responses

Status Description Schema
204 Success. No content is returned. -
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
409 Conflict. The request conflicts with the current state of the resource. -
422 Validation error. -
500 Internal server error. -
503 Service unavailable. A backend dependency the request needs is unreachable. The request was well formed and changed nothing, so the same call is worth retrying once the dependency is back; which one failed is recorded server-side, not in the response. -

GET /api/v1/tenants/{tenant_id}/subnets

List a tenant’s subnets

Returns the subnets the tenant named in the path owns.

Authentication: required (PasswordLogin or BearerJWT).

Parameters

Name In Type Required Description
tenant_id path string (uuid) yes Tenant identifier (UUID).

Responses

Status Description Schema
200 Successful response containing the list of subnets. SubnetListResponse
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
404 Tenant not found. -
422 Validation Error HTTPValidationError
500 Internal server error. -

Response Body (200, SubnetListResponse)

Field Type Required Default Example Description
subnets array of Subnet object no n/a Array of subnet objects. Empty array if none are found.
total integer yes n/a 0, 1, 12 Total count of subnets returned.

Example

{
   "subnets" : [
      {
         "addr_count" : 12,
         "addr_external" : false,
         "created_at" : "2026-06-29T12:00:00Z",
         "gateway" : "192.168.50.1",
         "id" : "36a8995f-026b-47e1-88bd-7fb9e7239995",
         "ip_range_base" : "192.168.50.0",
         "ip_range_prefix" : 24,
         "mtu" : 1450,
         "name" : "storage-net",
         "range_end" : "192.168.50.200",
         "range_start" : "192.168.50.10",
         "segment_id" : 10,
         "segment_type" : "vlan",
         "tenant_id" : "00000000-0000-0000-0000-000000000001",
         "tunnel_udp_port" : 0,
         "updated_at" : "2026-06-29T12:30:00Z"
      }
   ],
   "total" : 1
}

POST /api/v1/tenants/{tenant_id}/subnets

Create a new subnet

Creates a subnet owned by the tenant named in the path. Two things answer 404 here, with different detail text: a tenant the realm does not hold (“Owning tenant not found”), and a tenant the caller may not reach (“Tenant not found”), which is refused before the handler so the code cannot be used to establish which tenants exist. The platform tenant itself cannot own a subnet: it is given one with the realm and no other, so a create addressed to it is answered 409. The window must hold at least as many addresses as the realm primary pool, the node ipranges. A node takes the window address at its own position in that pool. A smaller window is answered 400, however few nodes the realm has.

Authentication: required (PasswordLogin or BearerJWT).

Parameters

Name In Type Required Description
tenant_id path string (uuid) yes Tenant identifier (UUID).

Request Body (application/json, required)

Schema: SubnetCreateRequest

Request payload for creating a subnet.

Field Type Required Default Example Description
addr_external boolean no false false, true Selects the address-allocation model. False leaves PanFS managing the addresses, true hands that to an outside allocator. Fixed at create and cannot be changed afterwards.
gateway string, nullable no null 192.168.50.1, null Optional default gateway address, in the same family as ip_range_base.
ip_range_base string yes n/a 192.168.50.0, fd00:dead:beef:: Base IP address of the subnet range (IPv4 or IPv6).
ip_range_prefix integer yes n/a 24, 64 CIDR prefix length. 1-32 for IPv4, 1-128 for IPv6.
mtu integer no 0 0, 1450, 8950 The MTU of the subnet’s tenant interface on every realm node and every attached host. 0 selects the default, which is the realm’s primary network MTU less the encapsulation overhead (50 bytes on vxlan, 4 bytes on vlan): 1450 on vxlan over a 1500-byte primary network. Any other value, from 68 to 9216, has to leave that overhead within the primary network MTU. A larger one is refused with 400 naming the largest value that fits.
name string yes n/a storage-net, tenant_a-vlan10 Subnet name, unique per tenant. 3-31 chars from [A-Za-z0-9_-].
range_end string, nullable no null 192.168.50.200, null Upper bound of the usable-address window, which is the range the realm allocates its own node addresses from. Supply it together with range_start, or omit both and let the realm derive the window, as range_start describes.
range_start string, nullable no null 192.168.50.10, null Lower bound of the usable-address window, which is the range the realm allocates its own node addresses from. The window must hold at least as many addresses as the realm primary pool. A host attached from outside the realm is placed outside this window. Supply it together with range_end, or omit both and the realm derives the window from the first usable host, as deep as its primary pool, so the subnet as stored reports a window this request did not carry. The realm refuses with 400 when it cannot read that pool, and then the window has to be named. Nothing is derived for a subnet whose addresses an outside allocator owns.
segment_id integer no 0 0, 10, 100100 Network segment identifier: a VLAN ID when the realm runs 802.1Q and a VNI when it runs VXLAN. 0 leaves it unassigned. Otherwise the bound here is the wider of the two spaces, 1-16777215. The realm manager owns the narrower range that follows the realm segment type, and the uniqueness of the identifier across the realm. Fixed at create and cannot be changed afterwards.
tunnel_udp_port integer no 0 0, 4789 UDP port carrying a tunnelled segment, which is meaningful when the realm runs VXLAN. 0 leaves it unset and the realm manager applies its own default. Otherwise the bound is 1-65535. Fixed at create and cannot be changed afterwards.

Example

{
   "addr_external" : false,
   "gateway" : "192.168.50.1",
   "ip_range_base" : "192.168.50.0",
   "ip_range_prefix" : 24,
   "mtu" : 1450,
   "name" : "storage-net",
   "range_end" : "192.168.50.200",
   "range_start" : "192.168.50.10",
   "segment_id" : 10,
   "tunnel_udp_port" : 0
}

Responses

Status Description Schema
201 Subnet created successfully. SubnetCreateResponse
400 Bad request. The realm manager refused the subnet: a gateway outside the range, a window the stored CIDR does not hold, a window smaller than the realm primary pool, no window named on a realm whose primary pool cannot be read to derive one, an unknown allocation flag, the default tenant as owner, a segment identifier offered while the realm names no segment type, or a segment identifier outside the space that type defines. An mtu larger than the realm’s primary network MTU less the segment’s encapsulation overhead is refused here too, before the realm manager sees it, with the largest value that fits named in the detail. Input the request schema can judge for itself is refused before the realm manager sees it and comes back as 422. -
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
404 Tenant not found. -
409 Conflict. The name is already taken, the segment identifier is already held by another subnet, or the realm already carries subnets on a different network segment type. The name is unique within the owning tenant, while the segment identifier is unique across the whole realm, because the identifier space belongs to the physical interface every tenant on a node shares. One realm carries one segment type, so a create lands on the type the realm already holds. A create addressed to the platform tenant answers this too: it owns its subnet with the realm and is given no other. A create also answers this when the realm could not record an address for the new subnet on every node, because every node takes one address on every tenant subnet and the realm caps how many it holds in all. -
422 Validation error. The request schema settles the name charset and length, the address family, the prefix, the MTU bounds, the segment-identifier and encapsulation-port bounds, and every usable-window rule a single request can judge on its own. A cross-field refusal reports body as its location and names the offending field in the message. segment_type and segment_flags land here too, because the realm owns them and the create schema forbids fields it does not declare. -
500 Internal server error. -
503 Service unavailable. A backend dependency the request needs is unreachable. The request was well formed and changed nothing, so the same call is worth retrying once the dependency is back; which one failed is recorded server-side, not in the response. -

Response Body (201, SubnetCreateResponse)

Field Type Required Default Example Description
id string (uuid) yes n/a Server-assigned unique identifier of the created subnet.

Example

{
   "id" : "36a8995f-026b-47e1-88bd-7fb9e7239995"
}

GET /api/v1/tenants/{tenant_id}/subnets/{subnet_id}

Get subnet info

Returns details for a specific subnet by its UUID. A subnet outside the tenant named in the path is answered 404, so the code cannot be used to establish that one exists.

Authentication: required (PasswordLogin or BearerJWT).

Parameters

Name In Type Required Description
tenant_id path string (uuid) yes Tenant identifier (UUID).
subnet_id path string (uuid) yes UUID of the subnet

Responses

Status Description Schema
200 Successfully retrieved detailed information about the subnet. SubnetGetResponse
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
404 Subnet not found. -
422 Validation Error HTTPValidationError
500 Internal server error. -

Response Body (200, SubnetGetResponse)

Field Type Required Default Example Description
subnet Subnet object yes n/a Subnet entity with all available details.

Example

{
   "subnet" : {
      "addr_count" : 12,
      "addr_external" : false,
      "created_at" : "2026-06-29T12:00:00Z",
      "gateway" : "192.168.50.1",
      "id" : "36a8995f-026b-47e1-88bd-7fb9e7239995",
      "ip_range_base" : "192.168.50.0",
      "ip_range_prefix" : 24,
      "mtu" : 1450,
      "name" : "storage-net",
      "range_end" : "192.168.50.200",
      "range_start" : "192.168.50.10",
      "segment_id" : 10,
      "segment_type" : "vlan",
      "tenant_id" : "00000000-0000-0000-0000-000000000001",
      "tunnel_udp_port" : 0,
      "updated_at" : "2026-06-29T12:30:00Z"
   }
}

PATCH /api/v1/tenants/{tenant_id}/subnets/{subnet_id}

Update subnet

Updates an existing subnet using PATCH semantics. Only the fields provided in the request are modified, and a field provided as null is cleared. An empty patch is rejected with HTTP 422. The subnet must belong to the tenant named in the path: one owned by another tenant is answered 404.

Authentication: required (PasswordLogin or BearerJWT).

Parameters

Name In Type Required Description
tenant_id path string (uuid) yes Tenant identifier (UUID).
subnet_id path string (uuid) yes UUID of the subnet

Request Body (application/json, required)

Schema: SubnetPatchRequest

Request payload for updating a subnet via PATCH (partial update).

Only the supplied fields are modified, and a patch carrying no field at all is refused here, so it answers 422 with the same envelope every other patch schema uses. A field supplied as null is cleared, which is how a gateway is removed and how a usable window is withdrawn by clearing both of its endpoints. name and mtu carry no meaning when cleared, so an explicit null is refused for them.

addr_external is absent by design: the flag is fixed at create, and extra='forbid' refuses a request that carries it. The four network segment values are absent for the same reason, and a patch naming any of them is refused with that field in the error location.

Field Type Required Default Example Description
gateway string, nullable no null 192.168.50.254, null New default gateway address (IPv4 or IPv6). Null clears the gateway.
mtu integer, nullable no null 1450, 8950 New MTU. The MTU of the subnet’s tenant interface on every realm node and every attached host. 0 selects the default, which is the realm’s primary network MTU less the encapsulation overhead (50 bytes on vxlan, 4 bytes on vlan): 1450 on vxlan over a 1500-byte primary network. Any other value, from 68 to 9216, has to leave that overhead within the primary network MTU. A larger one is refused with 400 naming the largest value that fits.
name string, nullable no null
range_end string, nullable no null 192.168.50.220, null New upper bound of the usable-address window. Patchable on its own. Null clears it, and clearing both bounds together has the realm derive the window anew, as range_start describes.
range_start string, nullable no null 192.168.50.20, null New lower bound of the usable-address window. Patchable on its own, and the realm manager validates the resulting pair against the subnet CIDR. Null clears it. Clearing both bounds together has the realm derive the window anew, on the same terms a create naming none gets one, and is refused with 409 while a subnet the realm addresses itself has an attached host.

Example

{
   "gateway" : "192.168.50.254",
   "mtu" : 1500,
   "name" : "storage-net-2",
   "range_end" : "192.168.50.220",
   "range_start" : "192.168.50.20"
}

Responses

Status Description Schema
204 Subnet updated successfully. -
400 Bad request. The realm manager refused the result of moving one window endpoint against the record it stores, including a window left smaller than the realm primary pool, or a window cleared on a realm whose primary pool cannot be read to derive a new one. An mtu larger than the realm’s primary network MTU less the subnet’s encapsulation overhead is refused before the realm manager sees it, with the largest value that fits named in the detail. A patch the request schema can judge for itself comes back as 422. -
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
404 Subnet not found. -
409 Conflict. The name is already taken, the window overlaps another subnet of the same tenant, or it would strand an address already bound to the subnet. The platform tenant’s own subnet answers this too: it comes with the realm. A write of the window or the gateway answers this when the subnet it leaves behind would reserve an address one of the subnet’s attached hosts already holds. Clearing range_start and range_end has the realm derive the window anew, which cannot be known in advance, so it answers this whenever the subnet has an attached host. A subnet whose addresses an outside allocator owns reserves nothing for the realm, so a write to it never answers this for an attached host. -
422 Validation error. A patch carrying no field at all, a field that is fixed at create, a null on a field that cannot be cleared, or a window whose own two endpoints contradict each other. addr_external and the four network segment values land here because the patch schema forbids fields it does not declare. -
500 Internal server error. -
503 Service unavailable. A backend dependency the request needs is unreachable. The request was well formed and changed nothing, so the same call is worth retrying once the dependency is back; which one failed is recorded server-side, not in the response. -

DELETE /api/v1/tenants/{tenant_id}/subnets/{subnet_id}

Delete subnet

Deletes the specified subnet by its UUID. The realm manager removes every address bound to the subnet in the same transaction, so bound addresses do not refuse the delete. The subnet must belong to the tenant named in the path: one owned by another tenant is answered 404.

Authentication: required (PasswordLogin or BearerJWT).

Parameters

Name In Type Required Description
tenant_id path string (uuid) yes Tenant identifier (UUID).
subnet_id path string (uuid) yes UUID of the subnet

Responses

Status Description Schema
204 Subnet deleted successfully. -
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
404 Subnet not found. -
409 Conflict. This is the last subnet of a tenant that still owns volumes, whose addresses it carries. Delete those volumes, or create another subnet for the tenant, first. The platform tenant’s own subnet answers this too: it comes with the realm. -
422 Validation Error HTTPValidationError
500 Internal server error. -

GET /api/v1/tenants/{tenant_id}/subnets/{subnet_id}/mount-info

Get the mount targets on a subnet

Returns the realm directors holding an address on this subnet, together with the address list a client puts in its mount options as rmlist=(). A client inside a tenant network has no route to the primary network and no access to the realm CLI, so this call is where the addresses it mounts against come from. The volume name is not a parameter of this call, so no assembled mount URL is returned: GET /volumes already names the volumes.

Authentication: required (PasswordLogin or BearerJWT).

Parameters

Name In Type Required Description
tenant_id path string (uuid) yes Tenant identifier (UUID).
subnet_id path string (uuid) yes UUID of the subnet

Responses

Status Description Schema
200 Successfully retrieved the realm directors a client on this subnet can mount to. An empty directors array with an empty rmlist is a legitimate answer: the subnet exists and the realm holds no address on it yet, which is one of the states a subnet is in. A realm provisioning operation records those addresses. An empty rmlist beside a populated directors array is legitimate too, and it says the recorded addresses are ones mount_panfs cannot bootstrap against. MountInfoResponse
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
404 Subnet not found. -
422 Validation Error HTTPValidationError
500 Internal server error. -
503 Service unavailable. The realm manager could not be reached, or it kept changing its address table for long enough that no consistent snapshot could be read. -

Response Body (200, MountInfoResponse)

Field Type Required Default Example Description
directors array of DirectorEntry object no n/a The realm directors holding an address on this subnet, nodes carrying a realm-database replica first and then ordered by address. Every such director appears here, in either address family. An empty array means the subnet exists and carries no address records yet.
rmlist string yes n/a 10.20.0.11;10.20.0.12 The addresses for mount_panfs, joined with “;” in the order above and wrapped by the caller as rmlist=(). It narrows the directors array to replica nodes, which is what the option means and what keeps it under the five addresses mount_panfs accepts, and to IPv4, which is all that grammar can express. Empty when no replica node holds an IPv4 address here, even when directors is not empty.
subnet_id string (uuid) yes n/a UUID of the subnet the addresses belong to.
tenant_id string (uuid) yes n/a UUID of the tenant owning the subnet.

Example

{
   "directors" : [
      {
         "dataserial" : "SN-D01",
         "ip" : "10.20.0.11",
         "repset" : true,
         "status" : "online"
      },
      {
         "dataserial" : "SN-D02",
         "ip" : "10.20.0.12",
         "repset" : true,
         "status" : "online"
      },
      {
         "dataserial" : "SN-D03",
         "ip" : "10.20.0.13",
         "repset" : false,
         "status" : "online"
      }
   ],
   "rmlist" : "10.20.0.11;10.20.0.12",
   "subnet_id" : "36a8995f-026b-47e1-88bd-7fb9e7239995",
   "tenant_id" : "e97b94d6-9cc1-4325-a267-1064ae538ccd"
}

11.2 Host Attachments

Attaching and detaching individual hosts to a subnet’s tenant network.

Endpoint Summary

Path Summary Method
/api/v1/tenants/{tenant_id}/subnets/{subnet_id}/attachments List the subnet’s host attachments GET
/api/v1/tenants/{tenant_id}/subnets/{subnet_id}/attachments Attach a host to the subnet’s tenant network POST
/api/v1/tenants/{tenant_id}/subnets/{subnet_id}/attachments/{attachment_id} Get attachment detail GET
/api/v1/tenants/{tenant_id}/subnets/{subnet_id}/attachments/{attachment_id} Detach a host from the subnet DELETE

GET /api/v1/tenants/{tenant_id}/subnets/{subnet_id}/attachments

List the subnet’s host attachments

Returns the hosts attached to the subnet. Supply the underlay_ip query parameter to narrow the listing to one host, which is how a host that knows only its own address finds its record in a single call.

Authentication: required (PasswordLogin or BearerJWT).

Parameters

Name In Type Required Description
tenant_id path string (uuid) yes Tenant identifier (UUID).
subnet_id path string (uuid) yes UUID of the subnet the host attaches to
underlay_ip query string, nullable no Narrow the listing to the host with this address on the realm underlay.

Responses

Status Description Schema
200 Successful response containing the subnet’s attachments. Narrowed to a single host when the underlay_ip query parameter is given, which is how a host that knows only its own address finds its record in one call. AttachmentListResponse
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
404 Not found. No such subnet or no such attachment, and equally a subnet or an attachment owned by another tenant. The two are one answer on purpose, so a response code cannot be used to establish that a record exists where the caller cannot see it. -
422 Validation Error HTTPValidationError
500 Internal server error. -
503 Service unavailable. The attachment store is unreachable, so the record could neither be read nor written. The request itself was well formed, which is why this is a 5xx. -

Response Body (200, AttachmentListResponse)

Field Type Required Default Example Description
attachments array of Attachment object no n/a Array of attachment objects. Empty array if none are found.
total integer yes n/a 0, 1, 12 Total count of attachments returned.

Example

{
   "attachments" : [
      {
         "hostname" : "client-0",
         "id" : "8f14e45f-ceea-467a-9575-5c4bcbdcd2f1",
         "joined_at" : "2026-08-28T12:00:00Z",
         "mtu" : 1450,
         "overlay_ip" : "192.168.50.100",
         "overlay_prefix" : 24,
         "segment_id" : 4257216,
         "segment_type" : "vxlan",
         "subnet_id" : "36a8995f-026b-47e1-88bd-7fb9e7239995",
         "tenant_id" : "00000000-0000-0000-0000-000000000001",
         "tunnel" : {
            "mac" : "92:b2:99:59:f3:fa",
            "udp_port" : 4789,
            "vteps" : [
               "10.128.1.0",
               "10.128.1.1",
               "10.128.1.2"
            ]
         },
         "underlay_ip" : "10.128.9.0"
      }
   ],
   "total" : 1
}

POST /api/v1/tenants/{tenant_id}/subnets/{subnet_id}/attachments

Attach a host to the subnet’s tenant network

Records the host and allocates its address on the tenant network. The response carries the whole record, so the host builds its interface from it with no further call. A host already attached to this subnet answers 200 with the stored record and an unchanged address.

Authentication: required (PasswordLogin or BearerJWT).

Parameters

Name In Type Required Description
tenant_id path string (uuid) yes Tenant identifier (UUID).
subnet_id path string (uuid) yes UUID of the subnet the host attaches to

Request Body (application/json, required)

Schema: AttachmentCreateRequest

Request body for attaching one host to a subnet.

Field Type Required Default Example Description
hostname string no (empty string) client-0 Informational host name, carried for the operator and never interpreted.
overlay_ip string, nullable no null 192.168.50.100 Requested address on the tenant network. Must sit inside the subnet, outside the realm node pool and be unallocated. Omit to let the backend allocate one. Applied on first attach only, so a repeated create never moves a host.
underlay_ip string yes n/a 10.128.9.0 The host’s address on the realm underlay. Identifies the attachment inside its subnet, and on a tunnelled segment it is the endpoint the realm sends the host traffic to.

Example

{
   "hostname" : "client-0",
   "overlay_ip" : "192.168.50.100",
   "underlay_ip" : "10.128.9.0"
}

Responses

Status Description Schema
200 The host is attached already, and the stored record is returned unchanged. A repeated create never moves a host, so an address named in the request is not applied to a record that exists: changing a host address is a delete followed by a create. -
201 Host attached. The response carries the whole record, so the host builds its interface from it with no further call. tunnel is null on a segment the fabric carries itself. Attachment
400 Bad request. The named address sits outside the subnet or, on a subnet the realm addresses itself, inside the range_start-range_end window the realm allocates its own node addresses from or on the gateway the subnet’s router holds. It is equally a request that named no address at all on a subnet whose addresses an outside allocator owns, where the realm has none to give. Input the request schema can judge for itself is refused before this and comes back as 422. -
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
404 Not found. No such subnet or no such attachment, and equally a subnet or an attachment owned by another tenant. The two are one answer on purpose, so a response code cannot be used to establish that a record exists where the caller cannot see it. -
409 Conflict. The named address is already allocated in this subnet, the subnet has no free address left outside the window the realm allocates its own node addresses from, or it declares no window at all, so any usable address may be a node’s. It may also already hold the maximum number of attachments. -
422 Validation error. The request schema settles that each address parses and that the body carries no field it does not declare. The link-layer address, the segment identifier and the realm node list are assigned by the backend, so naming any of them lands here. -
500 Internal server error. -
503 Service unavailable. The attachment store is unreachable, so the record could neither be read nor written. The request itself was well formed, which is why this is a 5xx. -

Response Body (201, Attachment)

Field Type Required Default Example Description
hostname string no (empty string) Host name as supplied at create. Informational.
id string (uuid) yes n/a Unique identifier of the attachment.
joined_at string (date-time) yes n/a First attach time (UTC).
mtu integer yes n/a The MTU the host sets on its tenant interface, which is the MTU the realm nodes run the subnet at: the subnet’s declared MTU, or the realm’s primary network MTU less the encapsulation overhead when the subnet declares 0. Never 0.
overlay_ip string yes n/a The host’s address on the tenant network.
overlay_prefix integer yes n/a CIDR prefix length of the subnet, which the host applies to its own address.
segment_id integer yes n/a The subnet VLAN ID under one segment type and its VNI under the other. 0 means unassigned.
segment_type SubnetSegmentType (none, vlan, vxlan) yes n/a Kind of network segment the subnet is placed on, as the realm has it configured.
subnet_id string (uuid) yes n/a UUID of the subnet the host is attached to.
tenant_id string (uuid) yes n/a UUID of the tenant owning the subnet.
tunnel AttachmentTunnel object, nullable no null Tunnel recipe, present only when the segment is tunnelled and null otherwise.
underlay_ip string yes n/a 10.128.9.0 The host’s address on the realm underlay. Identifies the attachment inside its subnet, and on a tunnelled segment it is the endpoint the realm sends the host traffic to.

Example

{
   "hostname" : "client-0",
   "id" : "8f14e45f-ceea-467a-9575-5c4bcbdcd2f1",
   "joined_at" : "2026-08-28T12:00:00Z",
   "mtu" : 1450,
   "overlay_ip" : "192.168.50.100",
   "overlay_prefix" : 24,
   "segment_id" : 4257216,
   "segment_type" : "vxlan",
   "subnet_id" : "36a8995f-026b-47e1-88bd-7fb9e7239995",
   "tenant_id" : "00000000-0000-0000-0000-000000000001",
   "tunnel" : {
      "mac" : "92:b2:99:59:f3:fa",
      "udp_port" : 4789,
      "vteps" : [
         "10.128.1.0",
         "10.128.1.1",
         "10.128.1.2"
      ]
   },
   "underlay_ip" : "10.128.9.0"
}

GET /api/v1/tenants/{tenant_id}/subnets/{subnet_id}/attachments/{attachment_id}

Get attachment detail

Returns one attachment of the subnet by its UUID.

Authentication: required (PasswordLogin or BearerJWT).

Parameters

Name In Type Required Description
tenant_id path string (uuid) yes Tenant identifier (UUID).
subnet_id path string (uuid) yes UUID of the subnet the host attaches to
attachment_id path string (uuid) yes UUID of the attachment

Responses

Status Description Schema
200 Successfully retrieved detailed information about the attachment. Attachment
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
404 Not found. No such subnet or no such attachment, and equally a subnet or an attachment owned by another tenant. The two are one answer on purpose, so a response code cannot be used to establish that a record exists where the caller cannot see it. -
422 Validation Error HTTPValidationError
500 Internal server error. -
503 Service unavailable. The attachment store is unreachable, so the record could neither be read nor written. The request itself was well formed, which is why this is a 5xx. -

Response Body (200, Attachment)

Field Type Required Default Example Description
hostname string no (empty string) Host name as supplied at create. Informational.
id string (uuid) yes n/a Unique identifier of the attachment.
joined_at string (date-time) yes n/a First attach time (UTC).
mtu integer yes n/a The MTU the host sets on its tenant interface, which is the MTU the realm nodes run the subnet at: the subnet’s declared MTU, or the realm’s primary network MTU less the encapsulation overhead when the subnet declares 0. Never 0.
overlay_ip string yes n/a The host’s address on the tenant network.
overlay_prefix integer yes n/a CIDR prefix length of the subnet, which the host applies to its own address.
segment_id integer yes n/a The subnet VLAN ID under one segment type and its VNI under the other. 0 means unassigned.
segment_type SubnetSegmentType (none, vlan, vxlan) yes n/a Kind of network segment the subnet is placed on, as the realm has it configured.
subnet_id string (uuid) yes n/a UUID of the subnet the host is attached to.
tenant_id string (uuid) yes n/a UUID of the tenant owning the subnet.
tunnel AttachmentTunnel object, nullable no null Tunnel recipe, present only when the segment is tunnelled and null otherwise.
underlay_ip string yes n/a 10.128.9.0 The host’s address on the realm underlay. Identifies the attachment inside its subnet, and on a tunnelled segment it is the endpoint the realm sends the host traffic to.

Example

{
   "hostname" : "client-0",
   "id" : "8f14e45f-ceea-467a-9575-5c4bcbdcd2f1",
   "joined_at" : "2026-08-28T12:00:00Z",
   "mtu" : 1450,
   "overlay_ip" : "192.168.50.100",
   "overlay_prefix" : 24,
   "segment_id" : 4257216,
   "segment_type" : "vxlan",
   "subnet_id" : "36a8995f-026b-47e1-88bd-7fb9e7239995",
   "tenant_id" : "00000000-0000-0000-0000-000000000001",
   "tunnel" : {
      "mac" : "92:b2:99:59:f3:fa",
      "udp_port" : 4789,
      "vteps" : [
         "10.128.1.0",
         "10.128.1.1",
         "10.128.1.2"
      ]
   },
   "underlay_ip" : "10.128.9.0"
}

DELETE /api/v1/tenants/{tenant_id}/subnets/{subnet_id}/attachments/{attachment_id}

Detach a host from the subnet

Removes the attachment record and frees the address it held. The call programs nothing, so there is no outcome to report: the realm withdraws the forwarding state on its own, after which the host is unreachable on the tenant network even if it keeps its interface.

Authentication: required (PasswordLogin or BearerJWT).

Parameters

Name In Type Required Description
tenant_id path string (uuid) yes Tenant identifier (UUID).
subnet_id path string (uuid) yes UUID of the subnet the host attaches to
attachment_id path string (uuid) yes UUID of the attachment

Responses

Status Description Schema
204 Host detached. The call removes the record and programs nothing, so there is no outcome to report. The realm withdraws the forwarding state on its own. -
401 Unauthorized. Authentication required. -
403 Forbidden. Insufficient permissions. -
404 Not found. No such subnet or no such attachment, and equally a subnet or an attachment owned by another tenant. The two are one answer on purpose, so a response code cannot be used to establish that a record exists where the caller cannot see it. -
422 Validation Error HTTPValidationError
500 Internal server error. -
503 Service unavailable. The attachment store is unreachable, so the record could neither be read nor written. The request itself was well formed, which is why this is a 5xx. -