# Packages API

Read your unlimited proxy packages and manage each package's allowed IPs. Writes need a Bearer API key; a signed-in documentation session can read but not write. Every write is one call: there is no precondition header, and the latest successful write wins.

## List and read

```http
GET /api/v1/packages
GET /api/v1/packages/{packageId}
Authorization: Bearer lit_...
```

| Parameter | Rules |
| --- | --- |
| `limit` | Integer 1–100; default 50. |
| `cursor` | The previous page's `page.nextCursor`, sent back unchanged. |

```json
{
  "data": {
    "id": 45,
    "status": "active",
    "product": { "key": "dc-shared-rotating", "name": "DC Shared Rotating" },
    "country": "us",
    "quantity": 2,
    "addOns": ["high-speed"],
    "limits": { "speedMbps": 20, "connections": 300, "requestsPerSec": 25 },
    "renewsAt": "2027-01-01T00:00:00.000Z",
    "endsAt": null,
    "tokenIds": [12345, 12346]
  }
}
```

An unknown, unowned, or unparseable `packageId` is `404 PACKAGE_NOT_FOUND`.

### Status

| `status` | Meaning | Dates |
| --- | --- | --- |
| `awaiting-payment` | Ordered, not yet paid. | Both `null`. |
| `active` | Paying, including a period already scheduled to end. | `renewsAt` is the next renewal, or `endsAt` is when it stops if it will not renew. |
| `suspended` | Payment failed or the package was paused. | `endsAt` when known, else `null`. |
| `ended` | No longer active. | `endsAt` is when it ended. |
| `setup-failed` | Provisioning did not complete. | Both `null`. |

`product.key` is the catalog family key and `product.name` its display name. `addOns` lists public add-on names: `high-speed`, `more-allowed-ips`, `multi-location`. `limits` is the effective per-token speed, connection, and request-rate ceiling for this package's configuration, or `null` when it cannot be resolved. `tokenIds` lists the package's current tokens; read each one with [Tokens](/docs/api/tokens).

## Allowed IPs

A package's allowed-IP list lets it accept passwordless connections from exact public IPv4 source addresses. It applies to every ready proxy endpoint in the package; each of those tokens also reports its own passwordless endpoint as `ipAuth` (see [Tokens](/docs/api/tokens)). Credentials keep working whether or not the list is empty. IPv6 is unsupported; contact support.

```http
GET /api/v1/packages/{packageId}/allowed-ips
Authorization: Bearer lit_...
```

```json
{
  "data": {
    "packageId": 45,
    "limit": 50,
    "ips": ["8.8.8.8", "9.9.9.9"],
    "available": true,
    "unavailableCode": null
  }
}
```

`limit` is `1` for a package without the add-on, or `50` with `more-allowed-ips`. `available: false` means the allowed-IP feature itself is unavailable right now; `unavailableCode` then names why.

### Change the list

Set the whole list at once:

```http
PUT /api/v1/packages/{packageId}/allowed-ips
Authorization: Bearer lit_...
Content-Type: application/json

{"ips": ["8.8.8.8", "9.9.9.9"]}
```

All-or-nothing: one invalid IP rejects the whole request with `400 INVALID_IP` naming it, and a list over `limit` is `409 ALLOWED_IPS_LIMIT_REACHED`. Send `{"ips": []}` to clear the list.

Add or remove a single IP, with no need to read the list first:

```http
PUT /api/v1/packages/{packageId}/allowed-ips/{ip}
DELETE /api/v1/packages/{packageId}/allowed-ips/{ip}
Authorization: Bearer lit_...
```

Both take no body. Adding an IP already on the list, or removing one already absent, is a successful no-op. A remove only marks the row revoked, so its audit history stays; it does not reappear in `ips`.

Every allowed-IP write returns the same `{data: ...}` shape shown above.

### Safety and limits

Adding an IP grants passwordless access from that source to every ready endpoint in the package; removing one takes access away. Writes to one package are limited to 20 per 60 seconds, on top of the per-key limit; see [rate limits](/docs/api#rate-limits).

### Allowed IP errors

| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `INVALID_IP` | The address is not an exact public IPv4 literal. The message names it. |
| `400` | `IPV6_UNSUPPORTED` | An IPv6 address was sent. Contact support. |
| `400` | `INVALID_REQUEST_BODY` | The set-the-list body is not exactly `{"ips": [...]}`. |
| `404` | `PACKAGE_NOT_FOUND` | Unknown, unowned, or malformed `packageId`. |
| `409` | `ALLOWED_IPS_LIMIT_REACHED` | The list would exceed `limit`. Contact support to raise it. |
| `409` | `PACKAGE_INACTIVE` | The package is not active. Removing IPs still works. |
| `409` | `CONCURRENT_CHANGE` | Another write to this package landed at the same moment. Retry the same request. |
| `429` | `RATE_LIMITED` | Over the per-key or per-package limit. Wait for `Retry-After`. |
| `503` | `ALLOWED_IPS_UNAVAILABLE` | Allowed-IP storage or port allocation is temporarily unavailable. Retry after `Retry-After`. |

Every code is described on [API errors](/docs/api/errors); schemas are in the [OpenAPI contract](/docs/openapi.json).

## Multi-location proxies

A Multi-location proxy's target city and carrier are token-level settings, not package-level: read and change them with [Tokens: Location](/docs/api/tokens#location).
