## GET /api/v2/products/vps

**List regular VPS products**

List fixed-cycle VPS products that can be ordered through `POST /api/v2/orders`. This catalog intentionally excludes the pay-as-you-go Cloud VPS tier; use `GET /api/v2/vps/payg/deployments` for PAYG deployment options. Use `data[].slug` as `items[].productSlug` when ordering a regular VPS, and only order products where `available` is true. `billing.billingCycle` and `billingCycles[]` describe fixed billing cycles; this endpoint does not emit `isPayg` because every listed product is non-PAYG.

### Related Endpoints

- `GET /api/v2/products/shared-hosting`: List shared-hosting products
- `GET /api/v2/products/domains`: List domain products
- `GET /api/v2/products/domains/{tld}`: Get TLD pricing and registry requirements

### Headers

- `Accept`: application/json
- No authorization header required.

### Parameters

- `limit` (query, integer) [min: 1, max: 100]: Maximum VPS products to return in this page. Defaults to 20 and is capped at 100. Example: `20`
- `cursor` (query, string): Opaque cursor returned as `nextCursor` from the previous page.
- `locale` (query, string): Optional label locale. Supported values are `en` and `sv`; unsupported values fall back to English. Example: `en`
  Allowed values: en, sv

### Request Example

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

### Response Schema

- `data` (array<object>, required)
- `data[].id` (string, required): Public VPS product ID. Use `slug` for orders; keep `id` for product detail lookups. Example: `vpsprod_01hxa3b4c5d6e7f8g9h0j1k2m3`
- `data[].slug` (string, required): Canonical value to send as `items[].productSlug` in `POST /api/v2/orders`. Example: `vps-xs`
- `data[].tier` (string, required): Short product tier used for display and sorting. Example: `xs`
- `data[].name` (string, required) Example: `VPS XS`
- `data[].resources` (object, required)
- `data[].resources.cpuCores` (number,null, required): Included CPU cores for the base plan. Example: `2`
- `data[].resources.memoryGb` (number,null, required): Included memory in GiB for the base plan. Example: `4`
- `data[].resources.storageGb` (number,null, required): Included storage in GiB for the base plan. Example: `80`
- `data[].bandwidth` (object, required)
- `data[].bandwidth.limitGb` (number,null, required): Included transfer allowance in GB. Example: `1024`
- `data[].billing` (object, required)
- `data[].billing.amount` (number, required): Amount for the primary/default billing cycle. Example: `99`
- `data[].billing.currencyCode` (string, required) Example: `SEK`
- `data[].billing.billingCycle` (string, required) Example: `monthly`
  Allowed values: monthly, quarterly, semiannually, annually, biennially, triennially, free
- `data[].billingCycles` (array<object>, required): Enabled fixed billing cycles. Use one of these `billingCycle` values in the order item.
- `data[].billingCycles[].billingCycle` (string, required) Example: `monthly`
  Allowed values: monthly, quarterly, semiannually, annually, biennially, triennially, free
- `data[].billingCycles[].amount` (number, required) Example: `99`
- `data[].billingCycles[].currencyCode` (string, required) Example: `SEK`
- `data[].billingCycles[].setupAmount` (number,null, required) Example: `null`
- `data[].billingCycles[].isPrimary` (boolean, required) Example: `true`
- `data[].availabilityStatus` (string, required): `available` is orderable; `out_of_stock` or `hidden` means do not create an order. Example: `available`
  Allowed values: available, out_of_stock, hidden
- `data[].available` (boolean, required) Example: `true`
- `data[].reason` (string,null, required): Customer-facing reason when `available` is false. Example: `null`
- `data[].configurableOptions` (array<object>, required): Optional order-time configuration. Send option values under the order item's `configurableOptions` object using these `key` values. For operating system selection, either send top-level `os` or `configurableOptions.operatingSystem` with the selected choice `value`.
- `data[].configurableOptions[].key` (string, required): Canonical option key such as `operatingSystem`, `bandwidthGb`, `additionalStorageGb`, `backupSlots`, `snapshotSlots`, or `ipv6`. Example: `operatingSystem`
- `data[].configurableOptions[].label` (string, required) Example: `Operating system`
- `data[].configurableOptions[].type` (string, required) Example: `select`
  Allowed values: slider, quantity, toggle, input, select
- `data[].configurableOptions[].default` (string | number | boolean | null, optional): Default value for the option. Type depends on `type`. Example: `ubuntu-24-04`
- `data[].configurableOptions[].choices` (array<object>, optional): Present for select options.
- `data[].configurableOptions[].choices[].value` (string, required) Example: `ubuntu-24-04`
- `data[].configurableOptions[].choices[].label` (string, required) Example: `Ubuntu 24.04`
- `data[].configurableOptions[].choices[].osTemplateId` (string, optional): Present for operating-system choices; accepted anywhere an OS template public ID is documented. Example: `os_01hxa3b4c5d6e7f8g9h0j1k2m3`
- `data[].configurableOptions[].min` (number, optional): Minimum value for slider or quantity options. Example: `1024`
- `data[].configurableOptions[].max` (number, optional): Maximum value for slider or quantity options. Example: `10240`
- `data[].configurableOptions[].step` (number, optional): Increment for slider or quantity options. Example: `1024`
- `data[].configurableOptions[].includedAtBase` (number, optional): Quantity included in the base plan before option pricing applies. Example: `1024`
- `data[].configurableOptions[].unit` (string,null, optional) Example: `GB`
- `data[].configurableOptions[].pricing` (array<object>, optional): Per-cycle option pricing. For sliders/quantity options this is price per unit above `includedAtBase`; for toggles it is the flat enabled price.
- `data[].configurableOptions[].pricing[].billingCycle` (string, required) Example: `monthly`
- `data[].configurableOptions[].pricing[].amount` (number, required) Example: `0.02`
- `data[].configurableOptions[].pricing[].currencyCode` (string, required) Example: `SEK`
- `data[].configurableOptions[].required` (boolean, optional): Present for input options. Example: `false`
- `hasMore` (boolean, required) Example: `false`
- `nextCursor` (string,null, required) Example: `null`

### Responses

#### 200 - Cursor-paginated regular VPS product catalog.
```json
{
  "data": [
    {
      "id": "vpsprod_01hxa3b4c5d6e7f8g9h0j1k2m3",
      "slug": "vps-xs",
      "tier": "xs",
      "name": "VPS XS",
      "resources": {
        "cpuCores": 2,
        "memoryGb": 4,
        "storageGb": 80
      },
      "bandwidth": {
        "limitGb": 1024
      },
      "billing": {
        "amount": 99,
        "currencyCode": "SEK",
        "billingCycle": "monthly"
      },
      "billingCycles": [
        {
          "billingCycle": "monthly",
          "amount": 99,
          "currencyCode": "SEK",
          "setupAmount": null,
          "isPrimary": true
        },
        {
          "billingCycle": "annually",
          "amount": 990,
          "currencyCode": "SEK",
          "setupAmount": null,
          "isPrimary": false
        }
      ],
      "availabilityStatus": "available",
      "available": true,
      "reason": null,
      "configurableOptions": [
        {
          "key": "operatingSystem",
          "label": "Operating system",
          "type": "select",
          "default": "ubuntu-24-04",
          "choices": [
            {
              "value": "ubuntu-24-04",
              "label": "Ubuntu 24.04",
              "osTemplateId": "os_01hxa3b4c5d6e7f8g9h0j1k2m3"
            }
          ]
        },
        {
          "key": "bandwidthGb",
          "label": "Bandwidth",
          "type": "slider",
          "min": 1024,
          "max": 10240,
          "step": 1024,
          "default": 1024,
          "includedAtBase": 1024,
          "unit": "GB",
          "pricing": [
            {
              "billingCycle": "monthly",
              "amount": 0.02,
              "currencyCode": "SEK"
            }
          ]
        }
      ]
    }
  ],
  "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"
    }
  ]
}
```

#### 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"
}
```
