# Add-ons — PapaProxy.net API reference

Page: https://papaproxy.net/developers/reference/add-ons.php
Last updated: November 9, 2026
Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/

### List available add-ons

`GET /api/v1/billing/services/{service_id}/addons/available`

- Access: API key required

Add-ons you can buy for this service, with prices and your current wallet balance. Each add-on lists its `price_cents` and a `params_schema` that describes the `params` to send when you buy it. Add-ons that are already active are not listed. Works with any valid API key.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `service_id (path)` | string (uuid) | yes | ID of the service. |

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons/available" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons/available",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons/available", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons/available');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons/available", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — Available addons for a given service.

Example response:

```json
{
  "service_id": "cb532178-5b03-506a-84ea-0230ff28576b",
  "addons": [
    {
      "addon_type": "udp_unlock",
      "display_name": "UDP Unlock",
      "description": "Enable UDP traffic through your proxy service.",
      "price_cents": 4750,
      "params_schema": null
    },
    {
      "addon_type": "extra_whitelist_ip",
      "display_name": "Extra Whitelist IPs",
      "description": "Add more IPs to your whitelist beyond the free quota.",
      "price_cents": 100,
      "params_schema": {
        "type": "object",
        "properties": {
          "ip_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000
          }
        },
        "required": [
          "ip_count"
        ]
      }
    }
  ],
  "wallet_balance_cents": 1997
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `service_id` | string (uuid) | yes | ID of the service. |
| `addons` | array of object | yes |  |
| `addons[].addon_type` | string | yes | Add-on type: `udp_unlock`, `extra_connections`, or `extra_whitelist_ip`. |
| `addons[].display_name` | string | yes | Name shown in the Dashboard. |
| `addons[].description` | string | yes |  |
| `addons[].price_cents` | integer | yes | Price in cents. |
| `addons[].params_schema` | object, nullable | no | JSON Schema of the `params` to send when buying this add-on. |
| `wallet_balance_cents` | integer | yes | Your wallet balance, in cents. |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `404` — Service not found, or it doesn't belong to your account.

  Example:

  ```json
  {"detail": "Service 3fa85f64-5717-4562-b3fc-2c963f66afa6 not found."}
  ```

- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### List active add-ons

`GET /api/v1/billing/services/{service_id}/addons`

- Access: API key required

Active add-ons on this service: type, status, purchase price, parameters, and activation date. Canceled add-ons are not listed. Works with any valid API key.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `service_id (path)` | string (uuid) | yes | ID of the service. |

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — Active addons for a given service.

Example response:

```json
{
  "service_id": "cb532178-5b03-506a-84ea-0230ff28576b",
  "addons": [
    {
      "addon_id": "fc9f2062-7726-5122-ab7f-d6a7814ed548",
      "service_id": "cb532178-5b03-506a-84ea-0230ff28576b",
      "addon_type": "extra_connections",
      "status": "active",
      "price_cents": 3,
      "nominal_price_cents": 3,
      "params": {
        "conn_count": 1
      },
      "activated_at": "2026-11-07T14:30:43.459177Z"
    }
  ]
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `service_id` | string (uuid) | yes | ID of the service. |
| `addons` | array of object | yes |  |
| `addons[].addon_id` | string (uuid) | yes | ID of the add-on. |
| `addons[].service_id` | string (uuid) | yes | ID of the service. |
| `addons[].addon_type` | string | yes | Add-on type: `udp_unlock`, `extra_connections`, or `extra_whitelist_ip`. |
| `addons[].status` | string | yes |  |
| `addons[].price_cents` | integer | yes | Price in cents. |
| `addons[].nominal_price_cents` | integer | no | List price in cents, before discounts. · Default: `0` |
| `addons[].params` | object, nullable | no |  |
| `addons[].activated_at` | string (date-time) | yes | When it was activated (ISO 8601, UTC). |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `404` — Service not found, or it doesn't belong to your account.

  Example:

  ```json
  {"detail": "Service 3fa85f64-5717-4562-b3fc-2c963f66afa6 not found."}
  ```

- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### Buy an add-on

`POST /api/v1/billing/services/{service_id}/addons`

- Access: API key required · scope `services:write`

Buy an add-on and pay from your wallet. Get the add-on types, prices, and the `params` each one needs from `GET …/addons/available`.

- `extra_connections`: send `params.conn_count` (1 to 10,000). The total is the add-on `price_cents` × `conn_count`.
- `extra_whitelist_ip`: send `params.ip_count`, the number of extra whitelisted IPs.
- `udp_unlock`: no params. The total is the add-on `price_cents` (50% of the base plan price).
- Always send `expected_price_cents` with the total you showed the user. Without it the request fails with `409` and `detail.error = "PRICE_CONFIRMATION_REQUIRED"`; the response includes `actual_price_cents`. If it doesn't match the current price, the request fails with `409` and `detail.error = "PRICE_CHANGED"`. Nothing is charged in either case.
- Insufficient balance returns `402` with `detail.error = "insufficient_funds"`. Nothing is charged.
- An unknown `addon_type` or a missing parameter returns `400`.
- Idempotent on `idempotency_key`.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `service_id (path)` | string (uuid) | yes | ID of the service. |

Request body (JSON):

| Field | Type | Required | Description |
|---|---|---|---|
| `addon_type` | string | yes | Add-on type from `GET …/addons/available`: `udp_unlock`, `extra_connections`, or `extra_whitelist_ip`. |
| `idempotency_key` | string | yes | Unique key for this purchase. A retry with the same key returns the original result without charging again. |
| `params` | object, nullable | no | Add-on parameters, as described by `params_schema` in `GET …/addons/available`: `{"conn_count": N}` for `extra_connections`, `{"ip_count": N}` for `extra_whitelist_ip`, none for `udp_unlock`. |
| `expected_price_cents` | integer, nullable | yes | The total you showed the user, in cents. Without it the request fails with `409` `PRICE_CONFIRMATION_REQUIRED`; if it doesn't match the current price, with `409` `PRICE_CHANGED`. Nothing is charged in either case. · Minimum: 0 |

Request examples:

cURL:

```bash
curl -X POST "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"addon_type": "extra_connections", "params": {"conn_count": 1}, "expected_price_cents": 3, "idempotency_key": "addon-2026-001"}'
```

Python:

```python
import os

import requests

resp = requests.post(
    "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    json={
        "addon_type": "extra_connections",
        "params": {
            "conn_count": 1
        },
        "expected_price_cents": 3,
        "idempotency_key": "addon-2026-001"
    },
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "addon_type": "extra_connections",
    "params": {
      "conn_count": 1
    },
    "expected_price_cents": 3,
    "idempotency_key": "addon-2026-001"
  }),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'addon_type' => 'extra_connections',
        'params' => [
            'conn_count' => 1
        ],
        'expected_price_cents' => 3,
        'idempotency_key' => 'addon-2026-001'
    ]),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	req, err := http.NewRequest("POST", "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons", strings.NewReader(`{"addon_type": "extra_connections", "params": {"conn_count": 1}, "expected_price_cents": 3, "idempotency_key": "addon-2026-001"}`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `201` — Result of addon purchase.

Example response:

```json
{
  "addon_id": "fc9f2062-7726-5122-ab7f-d6a7814ed548",
  "service_id": "cb532178-5b03-506a-84ea-0230ff28576b",
  "addon_type": "extra_connections",
  "status": "active",
  "price_cents": 3,
  "nominal_price_cents": 3,
  "created_at": "2026-11-07T14:30:43.459177Z"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `addon_id` | string (uuid) | yes | ID of the add-on. |
| `service_id` | string (uuid) | yes | ID of the service. |
| `addon_type` | string | yes | Add-on type: `udp_unlock`, `extra_connections`, or `extra_whitelist_ip`. |
| `status` | string | yes |  |
| `price_cents` | integer | yes | Price in cents. |
| `nominal_price_cents` | integer | no | List price in cents, before discounts. · Default: `0` |
| `created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). |

- `400` — Unknown `addon_type`, or a required parameter is missing.

  Example (unknown type):

  ```json
  {"detail": "Unknown addon type: 'udp'"}
  ```


  Example (missing param):

  ```json
  {"detail": "conn_count is required for extra_connections (integer 1..10000)."}
  ```

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `402` — Insufficient wallet balance. Nothing is charged.

  Example:

  ```json
  {"detail": {"error": "insufficient_funds", "required_cents": 4750, "balance_cents": 1777}}
  ```

- `403` — The API key lacks scope `services:write`.

  Example:

  ```json
  {"detail": "API key is missing required scope: services:write"}
  ```

- `404` — Service not found, or it doesn't belong to your account.

  Example:

  ```json
  {"detail": "Service 3fa85f64-5717-4562-b3fc-2c963f66afa6 not found."}
  ```

- `409` — `expected_price_cents` is missing or doesn't match the current price. Nothing is charged.

  Example (price confirmation required):

  ```json
  {"detail": {"error": "PRICE_CONFIRMATION_REQUIRED", "actual_price_cents": 3, "message": "expected_price_cents is required to confirm the purchase total (current 3)"}}
  ```


  Example (price changed):

  ```json
  {"detail": {"error": "PRICE_CHANGED", "expected_price_cents": 1, "actual_price_cents": 3}}
  ```

- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### Cancel an add-on

`DELETE /api/v1/billing/services/{service_id}/addons/{addon_id}`

- Access: API key required · scope `services:write`

Cancel an active add-on. Unused time may be refunded to your wallet on a prorated basis. Idempotent: canceling an add-on that is already canceled returns success.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `service_id (path)` | string (uuid) | yes | ID of the service. |
| `addon_id (path)` | string (uuid) | yes | ID of the add-on. |

Request examples:

cURL:

```bash
curl -X DELETE "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.delete(
    "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons/3fa85f64-5717-4562-b3fc-2c963f66afa6",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons/3fa85f64-5717-4562-b3fc-2c963f66afa6", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons/3fa85f64-5717-4562-b3fc-2c963f66afa6');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("DELETE", "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/addons/3fa85f64-5717-4562-b3fc-2c963f66afa6", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — Result of disabling (cancelling) an addon.

Example response:

```json
{
  "addon_id": "fc9f2062-7726-5122-ab7f-d6a7814ed548",
  "service_id": "cb532178-5b03-506a-84ea-0230ff28576b",
  "addon_type": "extra_connections",
  "status": "cancelled",
  "message": "Addon disabled. Prorated refund: 3 cents.",
  "refund_cents": 3
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `addon_id` | string (uuid) | yes | ID of the add-on. |
| `service_id` | string (uuid) | yes | ID of the service. |
| `addon_type` | string | yes | Add-on type: `udp_unlock`, `extra_connections`, or `extra_whitelist_ip`. |
| `status` | string | yes |  |
| `message` | string | yes |  |
| `refund_cents` | integer | no | Prorated refund credited to the wallet for the unused portion of the current billing period (0 when nothing was refunded). · Default: `0` |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `services:write`.

  Example:

  ```json
  {"detail": "API key is missing required scope: services:write"}
  ```

- `404` — Add-on not found, or it doesn't belong to your account.
- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```

