# Services — PapaProxy.net API reference

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

### List your services

`GET /api/v1/customer/services`

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

List your services (paginated).

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `offset (query)` | integer | no | Number of items to skip. · Minimum: 0 · Default: `0` |
| `limit (query)` | integer | no | Page size. · Minimum: 1 · Maximum: 100 · Default: `50` |

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/services" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/customer/services",
    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/customer/services", {
  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/customer/services');
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/customer/services", 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` — Paginated service list.

Example response:

```json
{
  "services": [
    {
      "id": "3bd2e419-2dc6-52bb-9d74-0a4560d55f53",
      "owner_type": "customer",
      "owner_id": "8218fbc6-d67b-52fd-a639-cca399215d31",
      "product_id": "5234db61-2f19-5aa0-8e64-faa72f7d4b76",
      "product_name": "Starter 2.5M",
      "product_kind": "proxy_rotating",
      "product_display_name": "Starter 2.5M",
      "display_name": "DC Rotating 2500k requests",
      "owner_email": null,
      "status": "active",
      "params": {
        "sticky": true,
        "username": "pp_user_3fa85f64",
        "pool_class": "datacenter",
        "quota_kind": "requests",
        "period_days": 30,
        "session_mode": "sticky"
      },
      "started_at": "2026-11-07T13:41:50.351104Z",
      "expires_at": "2027-01-06T13:41:50.369505Z",
      "grace_until": null,
      "cancelled_at": null,
      "usage_requests_cur": null,
      "usage_bytes_cur": null,
      "is_trial": false,
      "is_auto_renew": true,
      "customer_note": null,
      "created_at": "2026-11-07T13:41:50.327149Z",
      "price_cents": 4900,
      "price_unit": "period",
      "active_addon_types": [],
      "renewal_price_cents": 4900,
      "renewal_coverage": "not_covered",
      "suspend_reason": null,
      "next_quota_grant_at": "2026-12-07T13:41:50.369505Z",
      "renew_allowed": true
    },
    {
      "id": "cb532178-5b03-506a-84ea-0230ff28576b",
      "owner_type": "customer",
      "owner_id": "8218fbc6-d67b-52fd-a639-cca399215d31",
      "product_id": "50ef87c9-6dd6-50ae-b2e5-bef6da689911",
      "product_name": "Datacenter Proxy US 500 IPs",
      "product_kind": "proxy_datacenter",
      "product_display_name": "Datacenter Proxy US 500 IPs",
      "display_name": "Datacenter Proxy US 500 IPs",
      "owner_email": null,
      "status": "active",
      "params": {
        "geo": [
          "US"
        ],
        "segments": [
          {
            "count": 500,
            "country": 197148
          }
        ],
        "total_ip": 500,
        "composite": true,
        "whitelist_ips": [
          "203.0.113.10"
        ]
      },
      "started_at": "2026-11-07T13:41:00.208626Z",
      "expires_at": "2027-01-06T13:41:02.315398Z",
      "grace_until": null,
      "cancelled_at": null,
      "usage_requests_cur": null,
      "usage_bytes_cur": null,
      "is_trial": false,
      "is_auto_renew": true,
      "customer_note": null,
      "created_at": "2026-11-07T13:40:54.881463Z",
      "price_cents": 9500,
      "price_unit": "period",
      "active_addon_types": [],
      "renewal_price_cents": 9500,
      "renewal_coverage": "not_covered",
      "suspend_reason": null,
      "next_quota_grant_at": "2026-12-07T13:41:02.315398Z",
      "renew_allowed": true
    }
  ],
  "total": 16,
  "offset": 0,
  "limit": 2
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `services` | array of object | yes |  |
| `services[].id` | string (uuid) | yes | Unique ID. |
| `services[].owner_type` | string | yes | Owner type (`customer`). |
| `services[].owner_id` | string (uuid) | yes | ID of your account. |
| `services[].product_id` | string (uuid) | yes | ID of the catalog product. |
| `services[].product_name` | string | no | Catalog product name. · Default: `""` |
| `services[].product_kind` | string | no | Product type: `proxy_datacenter`, `proxy_isp`, or `proxy_rotating`. Dedicated proxies report the underlying type and have `params.dedicated = true`. · Default: `""` |
| `services[].product_display_name` | string | no | Product name shown in the Dashboard. · Default: `""` |
| `services[].display_name` | string, nullable | no | Name shown in the Dashboard. |
| `services[].owner_email` | string, nullable | no | Email of the owner, if set. |
| `services[].status` | string | yes | Service status, for example `active`, `pending` (awaiting payment), or `suspended`. |
| `services[].params` | object, nullable | yes | Service configuration: countries and IP counts for proxy packages; `dedicated: true` for dedicated proxies. |
| `services[].started_at` | string (date-time), nullable | yes | When the service started (ISO 8601, UTC). |
| `services[].expires_at` | string (date-time), nullable | yes | When the current term ends (ISO 8601, UTC). |
| `services[].grace_until` | string (date-time), nullable | no | End of the grace period after expiration, if any. |
| `services[].cancelled_at` | string (date-time), nullable | yes | When the service was canceled, if it was. |
| `services[].usage_requests_cur` | integer, nullable | yes | Requests used in the current period (rotating proxies). |
| `services[].usage_bytes_cur` | integer, nullable | yes | Bandwidth used in the current period, in bytes. |
| `services[].is_trial` | boolean | no | Whether this is a trial service. · Default: `false` |
| `services[].is_auto_renew` | boolean | yes | Whether the service renews automatically. |
| `services[].customer_note` | string, nullable | no | Your note on the service, if any. |
| `services[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). |
| `services[].price_cents` | integer, nullable | no | Price in cents. |
| `services[].price_unit` | string, nullable | no | Billing unit of `price_cents`. |
| `services[].active_addon_types` | array of string | no | Types of the add-ons that are active on this service. |
| `services[].renewal_price_cents` | integer, nullable | no | Forecast charge at the next auto-renewal (base price + active add-ons), same resolution as the real renewal charge. None when not applicable (see renewal_coverage). |
| `services[].renewal_coverage` | string | no | Whether the next renewal is covered: `covered_by_balance`, `covered_by_card` (an automatic top-up will charge the saved card), `not_covered`, or `not_applicable`. · Default: `"not_applicable"` |
| `services[].suspend_reason` | string, nullable | no | Why the service is suspended: `quota_exhausted`, `expired`, or `other`. Null when it isn't suspended. |
| `services[].next_quota_grant_at` | string (date-time), nullable | no | When the next monthly request allowance is added (rotating services), if it falls within the paid term. |
| `services[].renew_allowed` | boolean, nullable | no | Whether the service can be renewed right now. |
| `total` | integer | yes | Total number of items across all pages. |
| `offset` | integer | no | Number of items skipped. · Default: `0` |
| `limit` | integer | no | Page size. · Default: `50` |

- `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:read`.

  Example:

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

- `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 a service

`POST /api/v1/customer/services`

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

Buy a catalog product (rotating proxies) and pay from your wallet balance. Idempotent: repeating the call with the same product and parameters returns the existing service without charging again. For proxy packages built from countries, use `POST /api/v1/billing/composite/order`; for dedicated proxies, use `POST /api/v1/billing/dedicated/order`.

Request body (JSON):

| Field | Type | Required | Description |
|---|---|---|---|
| `product_id` | string (uuid) | yes | ID of the catalog product. |
| `params` | object, nullable | no |  |
| `auto_renew` | boolean | no | Default: `true` |
| `expected_price_cents` | integer, nullable | no | The total charge you showed the user, in cents. Required for some products; if it's missing or doesn't match the current price, the request fails with HTTP 409 and nothing is charged. |

Request examples:

cURL:

```bash
curl -X POST "https://papaproxy.net/account_new/api/v1/customer/services" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"product_id": "5234db61-2f19-5aa0-8e64-faa72f7d4b76", "expected_price_cents": 4900}'
```

Python:

```python
import os

import requests

resp = requests.post(
    "https://papaproxy.net/account_new/api/v1/customer/services",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    json={
        "product_id": "5234db61-2f19-5aa0-8e64-faa72f7d4b76",
        "expected_price_cents": 4900
    },
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/services", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "product_id": "5234db61-2f19-5aa0-8e64-faa72f7d4b76",
    "expected_price_cents": 4900
  }),
});
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/customer/services');
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([
        'product_id' => '5234db61-2f19-5aa0-8e64-faa72f7d4b76',
        'expected_price_cents' => 4900
    ]),
]);
$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/customer/services", strings.NewReader(`{"product_id": "5234db61-2f19-5aa0-8e64-faa72f7d4b76", "expected_price_cents": 4900}`))
	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 service creation.

Example response:

```json
{
  "service": {
    "id": "3bd2e419-2dc6-52bb-9d74-0a4560d55f53",
    "owner_type": "customer",
    "owner_id": "8218fbc6-d67b-52fd-a639-cca399215d31",
    "product_id": "5234db61-2f19-5aa0-8e64-faa72f7d4b76",
    "product_name": "Starter 2.5M",
    "product_kind": "proxy_rotating",
    "product_display_name": "Starter 2.5M",
    "display_name": "DC Rotating 2500k requests",
    "owner_email": null,
    "status": "active",
    "params": {
      "sticky": true,
      "username": "pp_user_3fa85f64",
      "pool_class": "datacenter",
      "quota_kind": "requests",
      "period_days": 30,
      "session_mode": "sticky"
    },
    "started_at": "2026-11-07T13:41:50.351104Z",
    "expires_at": "2027-01-06T13:41:50.369505Z",
    "grace_until": null,
    "cancelled_at": null,
    "usage_requests_cur": null,
    "usage_bytes_cur": null,
    "is_trial": false,
    "is_auto_renew": true,
    "customer_note": null,
    "created_at": "2026-11-07T13:41:50.327149Z",
    "price_cents": 4900,
    "price_unit": "period",
    "active_addon_types": [],
    "renewal_price_cents": null,
    "renewal_coverage": "not_applicable",
    "suspend_reason": null,
    "next_quota_grant_at": "2026-12-07T13:41:50.369505Z",
    "renew_allowed": true
  },
  "invoice_url": null
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `service` | object | yes | A purchased service / subscription. |
| `service.id` | string (uuid) | yes | Unique ID. |
| `service.owner_type` | string | yes | Owner type (`customer`). |
| `service.owner_id` | string (uuid) | yes | ID of your account. |
| `service.product_id` | string (uuid) | yes | ID of the catalog product. |
| `service.product_name` | string | no | Catalog product name. · Default: `""` |
| `service.product_kind` | string | no | Product type: `proxy_datacenter`, `proxy_isp`, or `proxy_rotating`. Dedicated proxies report the underlying type and have `params.dedicated = true`. · Default: `""` |
| `service.product_display_name` | string | no | Product name shown in the Dashboard. · Default: `""` |
| `service.display_name` | string, nullable | no | Name shown in the Dashboard. |
| `service.owner_email` | string, nullable | no | Email of the owner, if set. |
| `service.status` | string | yes | Service status, for example `active`, `pending` (awaiting payment), or `suspended`. |
| `service.params` | object, nullable | yes | Service configuration: countries and IP counts for proxy packages; `dedicated: true` for dedicated proxies. |
| `service.started_at` | string (date-time), nullable | yes | When the service started (ISO 8601, UTC). |
| `service.expires_at` | string (date-time), nullable | yes | When the current term ends (ISO 8601, UTC). |
| `service.grace_until` | string (date-time), nullable | no | End of the grace period after expiration, if any. |
| `service.cancelled_at` | string (date-time), nullable | yes | When the service was canceled, if it was. |
| `service.usage_requests_cur` | integer, nullable | yes | Requests used in the current period (rotating proxies). |
| `service.usage_bytes_cur` | integer, nullable | yes | Bandwidth used in the current period, in bytes. |
| `service.is_trial` | boolean | no | Whether this is a trial service. · Default: `false` |
| `service.is_auto_renew` | boolean | yes | Whether the service renews automatically. |
| `service.customer_note` | string, nullable | no | Your note on the service, if any. |
| `service.created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). |
| `service.price_cents` | integer, nullable | no | Price in cents. |
| `service.price_unit` | string, nullable | no | Billing unit of `price_cents`. |
| `service.active_addon_types` | array of string | no | Types of the add-ons that are active on this service. |
| `service.renewal_price_cents` | integer, nullable | no | Forecast charge at the next auto-renewal (base price + active add-ons), same resolution as the real renewal charge. None when not applicable (see renewal_coverage). |
| `service.renewal_coverage` | string | no | Whether the next renewal is covered: `covered_by_balance`, `covered_by_card` (an automatic top-up will charge the saved card), `not_covered`, or `not_applicable`. · Default: `"not_applicable"` |
| `service.suspend_reason` | string, nullable | no | Why the service is suspended: `quota_exhausted`, `expired`, or `other`. Null when it isn't suspended. |
| `service.next_quota_grant_at` | string (date-time), nullable | no | When the next monthly request allowance is added (rotating services), if it falls within the paid term. |
| `service.renew_allowed` | boolean, nullable | no | Whether the service can be renewed right now. |
| `invoice_url` | string, nullable | no | Link to the invoice, if one was issued. |

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

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


### Get a service

`GET /api/v1/customer/services/{service_id}`

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

Get details of one of your services.

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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/customer/services/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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6", {
  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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6');
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/customer/services/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` — A purchased service / subscription.

Example response:

```json
{
  "id": "cb532178-5b03-506a-84ea-0230ff28576b",
  "owner_type": "customer",
  "owner_id": "8218fbc6-d67b-52fd-a639-cca399215d31",
  "product_id": "50ef87c9-6dd6-50ae-b2e5-bef6da689911",
  "product_name": "Datacenter Proxy US 500 IPs",
  "product_kind": "proxy_datacenter",
  "product_display_name": "Datacenter Proxy US 500 IPs",
  "display_name": "Datacenter Proxy US 500 IPs",
  "owner_email": null,
  "status": "active",
  "params": {
    "geo": [
      "US"
    ],
    "segments": [
      {
        "count": 500,
        "country": 197148
      }
    ],
    "total_ip": 500,
    "composite": true,
    "whitelist_ips": [
      "203.0.113.10"
    ]
  },
  "started_at": "2026-11-07T13:41:00.208626Z",
  "expires_at": "2027-01-06T13:41:02.315398Z",
  "grace_until": null,
  "cancelled_at": null,
  "usage_requests_cur": null,
  "usage_bytes_cur": null,
  "is_trial": false,
  "is_auto_renew": true,
  "customer_note": null,
  "created_at": "2026-11-07T13:40:54.881463Z",
  "price_cents": 9500,
  "price_unit": "period",
  "active_addon_types": [],
  "renewal_price_cents": 9500,
  "renewal_coverage": "not_covered",
  "suspend_reason": null,
  "next_quota_grant_at": "2026-12-07T13:41:02.315398Z",
  "renew_allowed": true
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string (uuid) | yes | Unique ID. |
| `owner_type` | string | yes | Owner type (`customer`). |
| `owner_id` | string (uuid) | yes | ID of your account. |
| `product_id` | string (uuid) | yes | ID of the catalog product. |
| `product_name` | string | no | Catalog product name. · Default: `""` |
| `product_kind` | string | no | Product type: `proxy_datacenter`, `proxy_isp`, or `proxy_rotating`. Dedicated proxies report the underlying type and have `params.dedicated = true`. · Default: `""` |
| `product_display_name` | string | no | Product name shown in the Dashboard. · Default: `""` |
| `display_name` | string, nullable | no | Name shown in the Dashboard. |
| `owner_email` | string, nullable | no | Email of the owner, if set. |
| `status` | string | yes | Service status, for example `active`, `pending` (awaiting payment), or `suspended`. |
| `params` | object, nullable | yes | Service configuration: countries and IP counts for proxy packages; `dedicated: true` for dedicated proxies. |
| `started_at` | string (date-time), nullable | yes | When the service started (ISO 8601, UTC). |
| `expires_at` | string (date-time), nullable | yes | When the current term ends (ISO 8601, UTC). |
| `grace_until` | string (date-time), nullable | no | End of the grace period after expiration, if any. |
| `cancelled_at` | string (date-time), nullable | yes | When the service was canceled, if it was. |
| `usage_requests_cur` | integer, nullable | yes | Requests used in the current period (rotating proxies). |
| `usage_bytes_cur` | integer, nullable | yes | Bandwidth used in the current period, in bytes. |
| `is_trial` | boolean | no | Whether this is a trial service. · Default: `false` |
| `is_auto_renew` | boolean | yes | Whether the service renews automatically. |
| `customer_note` | string, nullable | no | Your note on the service, if any. |
| `created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). |
| `price_cents` | integer, nullable | no | Price in cents. |
| `price_unit` | string, nullable | no | Billing unit of `price_cents`. |
| `active_addon_types` | array of string | no | Types of the add-ons that are active on this service. |
| `renewal_price_cents` | integer, nullable | no | Forecast charge at the next auto-renewal (base price + active add-ons), same resolution as the real renewal charge. None when not applicable (see renewal_coverage). |
| `renewal_coverage` | string | no | Whether the next renewal is covered: `covered_by_balance`, `covered_by_card` (an automatic top-up will charge the saved card), `not_covered`, or `not_applicable`. · Default: `"not_applicable"` |
| `suspend_reason` | string, nullable | no | Why the service is suspended: `quota_exhausted`, `expired`, or `other`. Null when it isn't suspended. |
| `next_quota_grant_at` | string (date-time), nullable | no | When the next monthly request allowance is added (rotating services), if it falls within the paid term. |
| `renew_allowed` | boolean, nullable | no | Whether the service can be renewed right now. |

- `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:read`.

  Example:

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

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


### Renew a service

`POST /api/v1/customer/services/{service_id}/renew`

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

Renew a service for another term and pay from your wallet balance. Pass `period_months` (1, 3, 6, or 12) to apply the term discount. A retry with the same `idempotency_key` returns the original result and doesn't charge again. Insufficient balance returns `400` (`Insufficient funds: need … cents`); nothing is charged.

Parameters:

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

Request body (JSON):

| Field | Type | Required | Description |
|---|---|---|---|
| `period_count` | integer | no | Minimum: 1 · Maximum: 12 · Default: `1` |
| `period_months` | integer, nullable | no | Renewal term in months (1, 3, 6, or 12). Overrides `period_count` and applies the term discount. |
| `idempotency_key` | string, nullable | no | Idempotency key; retries with the same key never charge twice. Can also be sent in the `Idempotency-Key` header. · Min length: 1 · Max length: 128 |

Request examples:

cURL:

```bash
curl -X POST "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/renew" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"period_months": 1, "idempotency_key": "renew-2026-001"}'
```

Python:

```python
import os

import requests

resp = requests.post(
    "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/renew",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    json={
        "period_months": 1,
        "idempotency_key": "renew-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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/renew", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "period_months": 1,
    "idempotency_key": "renew-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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/renew');
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([
        'period_months' => 1,
        'idempotency_key' => 'renew-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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/renew", strings.NewReader(`{"period_months": 1, "idempotency_key": "renew-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:

- `200` — Simple status update response.

Example response:

```json
{
  "service_id": "cb532178-5b03-506a-84ea-0230ff28576b",
  "status": "active",
  "message": "Service renewed for 1 month(s).",
  "refund_cents": null,
  "new_expires_at": "2027-01-06T13:41:02.315398Z",
  "charged_cents": 5700,
  "funding_source": null,
  "refund_base_cents": null
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `service_id` | string (uuid) | yes | ID of the service. |
| `status` | string | yes |  |
| `message` | string | yes |  |
| `refund_cents` | integer, nullable | no |  |
| `new_expires_at` | string (date-time), nullable | no |  |
| `charged_cents` | integer, nullable | no | Amount charged to your wallet, in cents. |
| `funding_source` | string, nullable | no |  |
| `refund_base_cents` | integer, nullable | no |  |

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

  Example:

  ```json
  {"detail": "Insufficient funds: need 9500 cents"}
  ```

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


### Cancel a service

`POST /api/v1/customer/services/{service_id}/cancel`

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

Cancel (terminate) a service. Unused paid time may be refunded to your wallet on a prorated basis.

Cancellations are limited per account. Shortly after a previous cancellation, the request fails with `409` and `detail.error = "SERVICE_CANCEL_COOLDOWN"`; `detail.retry_at` and `detail.retry_after_seconds` say when you can try again. To cancel sooner, contact support.

Parameters:

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

Request examples:

cURL:

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

Python:

```python
import os

import requests

resp = requests.post(
    "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/cancel",
    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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/cancel", {
  method: "POST",
  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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/cancel');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    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("POST", "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/cancel", 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` — Simple status update response.

| Field | Type | Required | Description |
|---|---|---|---|
| `service_id` | string (uuid) | yes | ID of the service. |
| `status` | string | yes |  |
| `message` | string | yes |  |
| `refund_cents` | integer, nullable | no |  |
| `new_expires_at` | string (date-time), nullable | no |  |
| `charged_cents` | integer, nullable | no | Amount charged to your wallet, in cents. |
| `funding_source` | string, nullable | no |  |
| `refund_base_cents` | integer, nullable | no |  |

- `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` — Service not found, or it doesn't belong to your account.

  Example:

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

- `409` — A cancellation cooldown is active for your account.

  Example:

  ```json
  {"detail": {"error": "SERVICE_CANCEL_COOLDOWN", "support_required": true, "retry_at": "2026-11-10T13:41:08Z", "retry_after_seconds": 252708}}
  ```

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


### Get usage statistics

`GET /api/v1/customer/services/{service_id}/usage`

- Access: API key required · scope `usage:read`

Request and connection statistics for a service over a time window.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `service_id (path)` | string (uuid) | yes | ID of the service. |
| `start (query)` | string (date-time) | yes | Start of the window (ISO 8601) |
| `end (query)` | string (date-time) | yes | End of the window (ISO 8601) |

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/usage?start=2026-11-06T12:00:00Z&end=2026-11-06T12:00:00Z" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/usage?start=2026-11-06T12:00:00Z&end=2026-11-06T12:00:00Z",
    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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/usage?start=2026-11-06T12:00:00Z&end=2026-11-06T12:00:00Z", {
  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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/usage?start=2026-11-06T12:00:00Z&end=2026-11-06T12:00:00Z');
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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/usage?start=2026-11-06T12:00:00Z&end=2026-11-06T12:00:00Z", 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` — Usage stats for a proxy service.

Example response:

```json
{
  "connections": 0,
  "bytes_used": 0,
  "window": {
    "start": "2026-11-01T00:00:00Z",
    "end": "2026-11-07T23:59:59Z"
  }
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `connections` | integer | no | Default: `0` |
| `bytes_used` | integer | no | Default: `0` |
| `window` | object | yes | Time window for usage stats. |
| `window.start` | string (date-time) | yes |  |
| `window.end` | string (date-time) | yes |  |

- `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 `usage:read`.

  Example:

  ```json
  {"detail": "API key is missing required scope: usage:read"}
  ```

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

