# Servers

Your servers: the list, one server in detail, its traffic, and renaming it. Reading needs `read`; renaming needs `manage`.

## List servers

`GET /servers` returns the account's servers, newest first. Cancelled and terminated servers are not listed.

| Query parameter | Meaning |
|---|---|
| `limit` | 1 to 100. Default 50. |
| `cursor` | The `next_cursor` of the previous page, such as `srv_120`. |

```bash
curl -sS "https://api.redswitches.com/v1/servers?limit=2" \
  -H "Authorization: Bearer $REDSWITCHES_API_KEY"
```

```ts
let cursor: string | undefined;
do {
  const page = await rs.servers.list({ limit: 100, cursor });
  for (const s of page.data) console.log(s.id, s.status, s.primary_ip);
  cursor = page.next_cursor ?? undefined;
} while (cursor);
```

```python
cursor = None
while True:
    page = client.servers.list(limit=100, cursor=cursor)
    for s in page.data:
        print(s.id, s.status, s.primary_ip)
    cursor = page.next_cursor
    if not cursor:
        break
```

```go
opts := &redswitches.ListOptions{Limit: 100}
for {
	page, err := client.Servers.List(ctx, opts)
	if err != nil {
		log.Fatal(err)
	}
	for _, s := range page.Data {
		fmt.Println(s.ID, s.Status, s.PrimaryIP)
	}
	if page.NextCursor == nil {
		break
	}
	opts.Cursor = *page.NextCursor
}
```

```json
{
  "data": [
    {
      "id": "srv_123",
      "label": "web-1",
      "product": "Instant Dedicated Server E-2388G",
      "location": { "city": "Amsterdam", "country": "NL" },
      "status": "active",
      "primary_ip": "203.0.113.10",
      "billing_cycle": "monthly",
      "next_due_date": "2026-12-15",
      "recurring_amount": "129.00",
      "currency": "USD",
      "created": "2026-11-15"
    },
    {
      "id": "srv_121",
      "label": null,
      "product": "Dedicated Server 2x Xeon Gold 6230",
      "location": { "city": "New York", "country": "US" },
      "status": "suspended",
      "primary_ip": "203.0.113.24",
      "billing_cycle": "annually",
      "next_due_date": "2027-03-01",
      "recurring_amount": "2280.00",
      "currency": "USD",
      "created": "2026-03-01"
    }
  ],
  "next_cursor": "srv_121"
}
```

`next_cursor` is the id of the last server on the page, or null when there are no more. A cursor of the wrong kind (not `srv_…`) is `400 invalid_parameter`.

| Field | Type | Meaning |
|---|---|---|
| `id` | string | `srv_<n>`. |
| `label` | string or null | Your own name for the server. |
| `product` | string | The plan name. |
| `location` | object or null | `city` and `country` (two-letter code or null). |
| `status` | string | `pending`, `active` or `suspended`. |
| `primary_ip` | string or null | The main IP address. |
| `billing_cycle` | string or null | `monthly`, `quarterly`, `semiannually`, `annually`, `biennially`, `triennially`, `one_time` or `free`. |
| `next_due_date` | string or null | The next renewal date (`YYYY-MM-DD`). |
| `recurring_amount` | string | The renewal price as a decimal string. |
| `currency` | string | The account's currency. |
| `created` | string or null | The date the server was ordered (`YYYY-MM-DD`). |

Passwords, management (IPMI) details and staff notes never appear in the API.

## Get a server

`GET /servers/{id}` returns the list fields plus the server's IP addresses, operating system, hardware and delivery state.

```bash
curl -sS https://api.redswitches.com/v1/servers/srv_123 \
  -H "Authorization: Bearer $REDSWITCHES_API_KEY"
```

```ts
const server = await rs.servers.get("srv_123");
if (server.ips !== "unavailable") {
  for (const a of server.ips.addresses) console.log(a.ip, a.primary);
}
```

```python
server = client.servers.get("srv_123")
if server.ips != "unavailable":
    for a in server.ips.addresses:
        print(a.ip, a.primary)
```

```go
server, err := client.Servers.Get(ctx, "srv_123")
if err != nil {
	log.Fatal(err)
}
fmt.Println(server.Status, server.Delivery)
```

```json
{
  "id": "srv_123",
  "label": "web-1",
  "product": "Instant Dedicated Server E-2388G",
  "location": { "city": "Amsterdam", "country": "NL" },
  "status": "active",
  "primary_ip": "203.0.113.10",
  "billing_cycle": "monthly",
  "next_due_date": "2026-12-15",
  "recurring_amount": "129.00",
  "currency": "USD",
  "created": "2026-11-15",
  "ips": {
    "as_of": "2026-11-15T09:20:00Z",
    "addresses": [
      { "ip": "203.0.113.10", "primary": true },
      { "ip": "2001:db8::10", "primary": false }
    ]
  },
  "os": {
    "ordered": "Ubuntu 24.04",
    "installed": { "as_of": "2026-11-15T09:20:00Z", "name": "Ubuntu 24.04", "installing": false }
  },
  "hardware": {
    "as_of": "2026-11-15T09:20:00Z",
    "cpu": "Intel Xeon E-2388G",
    "ram": "64 GB",
    "disks": "2 x 960 GB NVMe"
  },
  "delivery": "ready",
  "rescue": false
}
```

The extra blocks are collected from the server on a schedule, never live during your request. Each block carries its own `as_of`. When nothing current is known, the block is the string `"unavailable"`, never an empty list or zero:

```json
{ "ips": "unavailable", "hardware": "unavailable", "os": { "ordered": "Debian 12", "installed": "unavailable" } }
```

| Field | Type | Meaning |
|---|---|---|
| `ips` | object or `"unavailable"` | `as_of` and `addresses`: each `ip` and whether it is the `primary`. |
| `os.ordered` | string or null | The OS chosen when the server was ordered. |
| `os.installed` | object or `"unavailable"` | `as_of`, `name` (string or null) of what the server reports, and `installing` (true while an install runs). |
| `hardware` | object or `"unavailable"` | `as_of`, `cpu`, `ram`, `disks` as text (each may be null). |
| `delivery` | string or null | For instant servers: `awaiting_payment`, `assigning`, `installing`, `ready`, `attention` or `closed`. Null for other servers. |
| `rescue` | boolean | `true` while the server is in rescue mode (false when not known). See [Rescue](rescue.md). |

A server that does not exist and a server of another account give the same `404 not_found`.

## Traffic

`GET /servers/{id}/traffic` returns data used in the current period and the allowance.

```bash
curl -sS https://api.redswitches.com/v1/servers/srv_123/traffic \
  -H "Authorization: Bearer $REDSWITCHES_API_KEY"
```

```ts
const t = await rs.servers.traffic("srv_123");
if (t.used_bytes !== "unavailable" && t.allowance_bytes) {
  console.log(`${((t.used_bytes / t.allowance_bytes) * 100).toFixed(1)}% used`);
}
```

```python
t = client.servers.traffic("srv_123")
if t.used_bytes != "unavailable" and t.allowance_bytes:
    print(f"{t.used_bytes / t.allowance_bytes:.1%} used")
```

```go
t, err := client.Servers.Traffic(ctx, "srv_123")
if err != nil {
	log.Fatal(err)
}
fmt.Println(t.UsedBytes, t.AllowanceBytes, t.Unmetered)
```

```json
{
  "id": "srv_123",
  "used_bytes": 1840000000000,
  "unavailable_reason": null,
  "period_start": "2026-10-16T00:00:00Z",
  "period_end": "2026-11-15T00:00:00Z",
  "basis": "last_30_days",
  "as_of": "2026-11-15T09:00:00Z",
  "allowance_bytes": 50000000000000,
  "unmetered": false
}
```

| Field | Type | Meaning |
|---|---|---|
| `used_bytes` | integer or `"unavailable"` | Bytes used in the period. |
| `unavailable_reason` | string or null | When `used_bytes` is unavailable: `not_collected` (no current figure) or `ownership_changed_in_window` (the measured period started before you held the server, so it would include someone else's use). |
| `period_start`, `period_end` | string or null | The measured period (UTC). Null when unavailable. |
| `basis` | string or null | `billing_period` (the server's billing month) or `last_30_days` (a rolling 30-day total). Which one depends on the server's network. |
| `as_of` | string or null | When the figure was collected. |
| `allowance_bytes` | integer or null | The traffic included in the plan. Null when unmetered or unknown. |
| `unmetered` | boolean | True when traffic is not metered. |

When unavailable, the answer looks like this:

```json
{
  "id": "srv_123",
  "used_bytes": "unavailable",
  "unavailable_reason": "ownership_changed_in_window",
  "period_start": null,
  "period_end": null,
  "basis": null,
  "as_of": null,
  "allowance_bytes": 50000000000000,
  "unmetered": false
}
```

When the period started before you held the server, traffic is counted from the moment you took it over, with `basis: "since_ownership"`, wherever a starting figure was recorded. Only where none exists does it stay `unavailable`. Another customer's use is never shown.

## Rename a server

`POST /servers/{id}/label` sets your own name for a server. Needs `manage` and an `Idempotency-Key`. The label is only a name in the client area and the API; it does not change the server's hostname.

| Body field | Rule |
|---|---|
| `label` | Up to 60 characters: letters, digits, spaces, `.`, `-`, `_`. Runs of spaces become one. Control characters are refused. `""` clears the label. |

```bash
curl -sS -X POST https://api.redswitches.com/v1/servers/srv_123/label \
  -H "Authorization: Bearer $REDSWITCHES_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"label":"web-1"}'
```

```ts
await rs.servers.setLabel("srv_123", { label: "web-1" });
```

```python
client.servers.set_label("srv_123", label="web-1")
```

```go
_, err := client.Servers.SetLabel(ctx, "srv_123", &redswitches.LabelRequest{Label: "web-1"})
```

```json
{ "id": "srv_123", "label": "web-1" }
```

After clearing, `label` is null. Errors: `400 invalid_body` (bad label), `404 not_found`.
