> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basaltic.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Route tables and routes

> Which table a subnet uses is what makes it public or private — and exactly one route per destination.

## Route tables

Every VPC gets a default table named `<vpc-name>-private-rt` when it is created.
Subnets use it unless they supply a `route_table` reference. Table names are
unique per VPC. Long generated names are shortened with a hash.

Existing default tables formerly named `main` are renamed in place; their IDs,
routes and subnet associations stay unchanged. A numeric suffix resolves name
collisions. The default role is identified by `is_main`, independently of its
name. The historical name `main` remains reserved.

<Tabs>
  <Tab title="Console">
    Go to **Networking → Route Tables** and choose **Create Route Table**.
    Under **Route table details**, pick the **VPC** and give it a **Name**.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/route-tables
    { "vpc": "<vpc>", "name": "private" }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    basaltic network route-table create --vpc <vpc-id> --name private
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    rt, err := network.New(cfg).CreateRouteTable(ctx, &network.RouteTableCreateRequest{
        VPC: vpcID,
        Name:  "private",
    })
    ```
  </Tab>
</Tabs>

Route-table responses include the full `vpc` object instead of `vpc_id`, so
`vpc.id` and `vpc.name` identify the parent without a display-only lookup.
Creation still takes a `vpc` string reference.

A subnet embeds only a route-table summary (`id`, `crn`, `name`), which can
be null; it does not contain routes or repeat the VPC. Fetch the table's routes
when checking reachability. Individual route responses still carry
`route_table_id` to identify their table.

Deleting a table refuses in two cases: the main table cannot be deleted at all,
and a table still associated with subnets is refused with "reassociate them
first". Routes on a table go away with it.

## Routes

<Tabs>
  <Tab title="Console">
    Open the table under **Networking → Route Tables** and choose **Add
    Route**. Fill in **Destination CIDR**, then pick a **Target type** — **IP
    address**, **Internet Gateway**, **NAT Gateway** or **Egress-Only
    Gateway** — and the field below it changes to match. Confirm with **Add
    Route**.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/route-tables/{route_table_id}/routes
    { "destination_cidr": "10.1.0.0/16", "next_hop_ip": "10.0.1.9" }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    basaltic network route create <route-table-id> \
      --destination-cidr 10.1.0.0/16 --next-hop-ip 10.0.1.9
    ```

    Swap `--next-hop-ip` for `--target-internet-gateway`,
    `--target-nat-gateway` or `--target-egress-only-gateway`. Exactly one.
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    r, err := network.New(cfg).CreateRoute(ctx, routeTableID, &network.RouteCreateRequest{
        DestinationCIDR: "10.1.0.0/16",
        NextHopIP:    basaltic.String("10.0.1.9"),
    })
    ```
  </Tab>
</Tabs>

`destination` is a CIDR, and **exactly one** target field must be set:

<ResponseField name="next_hop_ip" type="string">
  A unicast next hop **inside this VPC's CIDR**, same family as the
  destination. Loopback, link-local, multicast and unspecified addresses are
  rejected, and so is anything outside the VPC. This is for appliances and NICs
  you run yourself — internet egress is not expressible this way, because a
  gateway target is what pins the route to your own VPC's uplink.
</ResponseField>

<ResponseField name="target_internet_gateway" type="string">
  The gateway must be attached, and attached to **this** VPC. Otherwise the
  create fails with "target internet gateway is not attached to a VPC" or
  "target internet gateway is attached to a different VPC".
</ResponseField>

<ResponseField name="target_nat_gateway" type="string">
  Supports IPv4 and IPv6. Use `0.0.0.0/0` and `::/0` for shared internet egress.
  An IPv6 route requires IPv6 on the gateway's hosting subnet. Adding a route
  does not allocate the gateway's addresses.
</ResponseField>

<ResponseField name="target_egress_only_gateway" type="string">
  IPv6 destinations only, and refused the other way round.
</ResponseField>

<Note>
  In the console this is **Target type → Egress-Only Gateway** on **Add
  Route**. The dialog checks the pairing as you type: naming an IPv4
  destination with an egress-only target is refused before submit.
</Note>

### One route per destination

A table holds at most one route to a given destination. A second one is refused
with `409`.

There is no equal-cost pair and no tie-break between two routes to the same
prefix, because the second one never gets created.

A route's `destination` and target are immutable; `PATCH` only takes
`description` and `tags`. To repoint a route, delete it and create the
replacement.
