## GET /api/v2/vps

**List VPS instances**

Return existing VPS instances with public IDs, canonical service/power status, billing, resources, primary IP, operating system, bandwidth, availability, tags, and pin state. Use `primaryIp=192.0.2.10` to find the public `vps_...` ID when the caller only knows an IP address; do not pass IP addresses or hostnames as `{id}` to detail/action routes. Use `data[].id` from this endpoint as the `{id}` path parameter for VPS details, power actions, backups, snapshots, networking, and console calls. This inventory can include both regular fixed-cycle VPS and pay-as-you-go Cloud VPS after deployment; inspect `billing.isPayg` instead of guessing from the name. `billing.isPayg: false` means a fixed-cycle VPS ordered from `GET /api/v2/products/vps` and `POST /api/v2/orders`; `billing.isPayg: true` means a PAYG Cloud VPS created from the Cloud VPS endpoints. Action gates live on the richer detail/status endpoints, not on this list response.

### Related Endpoints

- `GET /api/v2/vps/{id}`: Get VPS details
- `GET /api/v2/vps/metrics`: List live VPS metrics
- `GET /api/v2/vps/{id}/iso`: List VPS ISO media

### Headers

- `Accept`: application/json
- `Authorization`: Bearer YOUR_API_KEY
- Required API scope: `read:vm`

### Parameters

- `limit` (query, integer) [min: 1, max: 100]: Maximum VPS instances to return in this page.
- `cursor` (query, string): Opaque cursor from `nextCursor` for the next page.
- `include` (query, string): Optional repeated include. Currently supports `pendingMaintenance`; call as `?include=pendingMaintenance` to add `pendingMaintenance` and `pendingMaintenanceHasDeadline` to each item. Example: `pendingMaintenance`
  Allowed values: pendingMaintenance
- `primaryIp` (query, string): Exact primary IP address to find. Use this to get the public `vps_...` ID for a VPS when the caller only knows its IP address. Example: `192.0.2.10`
- `name` (query, string): Exact VPS display name to find. Matching is case-insensitive and returns the same paginated list shape. Example: `app-01`

### Request Example

```bash
curl -X GET "https://cloud.hostup.se/api/v2/vps" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
```

### Response Schema

- `data` (array<object>, optional)
- `data[].id` (string, required) Example: `vps_01hxa3b4c5d6e7f8g9h0j1k2m3`
- `data[].name` (string, required) Example: `app-01`
- `data[].primaryIp` (string,null, required) Example: `192.0.2.10`
- `data[].serviceStatus` (string, required) Example: `active`
  Allowed values: active, suspended, terminated, pending, cancelled, expired, fraud, unknown
- `data[].powerState` (string,null, required) Example: `running`
  Allowed values: running, stopped, starting, stopping, paused, provisioning, error, unknown, 
- `data[].operatingSystem` (object,null, required) Example: `{"displayName":"Ubuntu 24.04","family":"ubuntu","version":"24.04","variant":null}`
- `data[].billing` (object, required)
- `data[].billing.amount` (number, required) Example: `199`
- `data[].billing.currencyCode` (string, required) Example: `SEK`
- `data[].billing.billingCycle` (string,null, required): Canonical billing cycle. VPS services with `isPayg: true` still report `monthly` for summary display; use `isPayg` to distinguish PAYG Cloud VPS from fixed-cycle VPS. Example: `annually`
  Allowed values: monthly, quarterly, semiannually, annually, biennially, triennially, free, 
- `data[].billing.isPayg` (boolean, required): For VPS service/order billing, true means pay-as-you-go Cloud VPS and false means fixed-cycle/prepaid VPS. Non-VPS resources normally return false. Example: `false`
- `data[].billing.periodYears` (integer,null, optional) Example: `1`
- `data[].resources` (object, required)
- `data[].resources.cpuCores` (number, optional) Example: `2`
- `data[].resources.memoryGb` (number, optional) Example: `4`
- `data[].resources.storageGb` (number, optional) Example: `80`
- `data[].bandwidth` (object, required)
- `data[].bandwidth.usedGb` (number, required) Example: `12.5`
- `data[].bandwidth.limitGb` (number, required) Example: `1000`
- `data[].bandwidth.inboundGb` (number, required) Example: `4.2`
- `data[].bandwidth.outboundGb` (number, required) Example: `8.3`
- `data[].bandwidth.hasOverage` (boolean, required) Example: `false`
- `data[].availability` (object, required)
- `data[].availability.available` (boolean, required) Example: `true`
- `data[].availability.reason` (string,null, required) Example: `null`
- `data[].tags` (array<string>, required) Example: `["production"]`
- `data[].pinned` (boolean, required) Example: `false`
- `data[].createdAt` (string,null, required) Example: `2026-04-27T12:00:00.000Z`
- `data[].pendingMaintenance` (boolean, optional): Only present when the list route is called with include=pendingMaintenance. Example: `false`
- `data[].pendingMaintenanceHasDeadline` (boolean, optional): Only present together with pendingMaintenance. Example: `false`
- `data[].overdueInvoices` (array<object>, optional): Only present on single-detail routes when overdue invoices were queried.
- `hasMore` (boolean, optional) Example: `false`
- `nextCursor` (string,null, optional) Example: `null`

### Responses

#### 200 - Cursor-paginated list.
```json
{
  "data": [
    {
      "id": "vps_01hxa3b4c5d6e7f8g9h0j1k2m3",
      "name": "app-01",
      "primaryIp": "192.0.2.10",
      "serviceStatus": "active",
      "powerState": "running",
      "operatingSystem": {
        "displayName": "Ubuntu 24.04",
        "family": "ubuntu",
        "version": "24.04",
        "variant": null
      },
      "resources": {
        "cpuCores": 2,
        "memoryGb": 4,
        "storageGb": 80
      },
      "billing": {
        "amount": 199,
        "currencyCode": "SEK",
        "billingCycle": "monthly",
        "isPayg": false
      },
      "bandwidth": {
        "usedGb": 12.5,
        "limitGb": 1000,
        "inboundGb": 4.2,
        "outboundGb": 8.3,
        "hasOverage": false
      },
      "availability": {
        "available": true,
        "reason": null
      },
      "tags": [
        "production"
      ],
      "pinned": false,
      "createdAt": "2026-04-27T12:00:00.000Z",
      "pendingMaintenance": false,
      "pendingMaintenanceHasDeadline": false
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

#### 400 - Invalid request. The response body is an RFC 7807 Problem Details document.
```json
{
  "type": "https://developer.hostup.se/errors/invalid_request",
  "title": "Invalid request",
  "status": 400,
  "detail": "The request body failed validation.",
  "code": "invalid_request",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z",
  "errors": [
    {
      "pointer": "/items/0/domainName",
      "detail": "`domainName` is required.",
      "code": "invalid_request"
    }
  ]
}
```

#### 401 - Unauthorized. Authentication is required.
```json
{
  "type": "https://developer.hostup.se/errors/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Authentication is required.",
  "code": "unauthorized",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z"
}
```

#### 403 - Forbidden. The caller lacks a required scope or does not own the resource.
```json
{
  "type": "https://developer.hostup.se/errors/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "The caller lacks a required scope or does not own the resource.",
  "code": "forbidden",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z"
}
```

#### 404 - Not found. The resource does not exist or is not owned by the caller.
```json
{
  "type": "https://developer.hostup.se/errors/not_found",
  "title": "Not found",
  "status": 404,
  "detail": "The requested resource could not be found.",
  "code": "not_found",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z"
}
```

#### 429 - Rate limited. Retry after the limit resets. 429 responses include `Retry-After` seconds plus `X-RateLimit-*` headers.
```json
{
  "type": "https://developer.hostup.se/errors/rate_limit_exceeded",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests. Retry after the limit resets.",
  "code": "rate_limit_exceeded",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z"
}
```

#### 500 - Internal error. Retry later or contact support if the issue persists.
```json
{
  "type": "https://developer.hostup.se/errors/internal_error",
  "title": "Internal server error",
  "status": 500,
  "detail": "An unexpected error occurred. Retry later or contact support if the issue persists.",
  "code": "internal_error",
  "instance": "/api/v2/resource",
  "requestId": "req_01hxa3b4c5d6e7f8g9h0j1k2m3",
  "timestamp": "2026-04-27T12:34:56.000Z"
}
```
