# PapaProxy.net — complete developer documentation > REST API and MCP server for PapaProxy.net proxy services: the developer guide followed by the full API reference (every endpoint, parameter, and response field). Short index: https://papaproxy.net/llms.txt. Machine-readable spec: https://papaproxy.net/openapi.json. --- # PapaProxy.net — API and MCP for developers Page: https://papaproxy.net/developers.php PapaProxy.net Dashboard operations are available through a REST API and an MCP server. Both use the same API keys with scopes. ## Essentials - Base URL: `https://papaproxy.net/account_new/api/v1` - Authentication: `Authorization: Bearer ` header - Create a key: https://papaproxy.net/account_new/settings?locale=en#api-keys - OpenAPI 3.1 specification (machine-readable; agents can read it directly): https://papaproxy.net/openapi.json - API reference: https://papaproxy.net/developers/reference/ - API reference in Markdown (index with links to every endpoint group): https://papaproxy.net/developers/reference/index.md - All developer docs in one Markdown file: https://papaproxy.net/llms-full.txt - “Try it” console (Dashboard, sign-in required): https://papaproxy.net/account_new/docs?locale=en - MCP endpoint: `https://papaproxy.net/account_new/api/v1/mcp` (remote server, HTTP) - MCP server on GitHub (configuration examples, server.json): https://github.com/alexyoungpapaproxy/papaproxy-mcp - No key needed for: `GET /billing/countries`, `GET /billing/composite/pricing-info`, `POST /billing/composite/auto-plan`, `POST /billing/composite/quote`, `GET /billing/dedicated/countries`, and `POST /billing/dedicated/quote` - Rate limit: 60 requests per minute per account, shared by all API keys and the MCP server; per-IP and burst limits also apply. On HTTP 429, retry with exponential backoff (2, 4, 8 seconds) - No sandbox or test mode: orders, top-ups, and renewals charge your real balance. Test integrations with free quotes and the webhook test event - Webhooks: `X-FB-Signature: v1=` is HMAC-SHA256 over `{X-FB-Timestamp}.{raw body}`, keyed with the endpoint's full secret including the `whsec_` prefix; respond with 2xx within 10 seconds; see https://papaproxy.net/developers/reference/#webhooks - Errors: JSON with `detail` (a string or an object with `error` or `code`); see https://papaproxy.net/developers/reference/#errors - With a key, the MCP server gives the agent more than 50 tools ## Without a key ```bash curl -s "https://papaproxy.net/account_new/api/v1/billing/countries?product_kind=proxy_datacenter" curl -s -X POST https://papaproxy.net/account_new/api/v1/billing/composite/quote \ -H "Content-Type: application/json" \ -d '{"product_kind":"proxy_datacenter","period_months":1,"segments":[{"country":"US","count":100}]}' ``` Quote prices are in cents (`total_price_cents`, `monthly_price_cents`). ## MCP connection ```json { "mcpServers": { "papaproxy": { "url": "https://papaproxy.net/account_new/api/v1/mcp", "headers": { "Authorization": "Bearer fb_YOUR_API_KEY" } } } } ``` Claude Code: `claude mcp add --transport http papaproxy https://papaproxy.net/account_new/api/v1/mcp --header "Authorization: Bearer fb_YOUR_API_KEY"` ## Buying proxies 1. `list_countries` / `GET /billing/countries?product_kind=proxy_datacenter|proxy_isp` — countries and live stock. 2. `quote_composite` / `POST /billing/composite/quote` — free, non-binding price quote. 3. `order_composite` / `POST /billing/composite/order` — order paid from the account balance. Order fields: `product_kind` (`proxy_datacenter` or `proxy_isp`), `segments` (`[{"country":"US","count":300}]`, ISO-2), `period_months` (1, 3, 6, 12), `whitelist_ips` (required; otherwise HTTP 422 before any charge), `use_balance: true` (apply the balance first; the default `false` creates the service as `pending` until it's paid in the Dashboard), `idempotency_key`. A retry with the same `idempotency_key` returns the original order without a second charge; without a key, every request creates a new order. Package minimums: 100 IPs per order and 10 IPs per country; adding IPs follows the same rule. A quote below 100 IPs in total returns HTTP 422, and a country below 10 IPs is listed in `errors` and left out of the total. Dedicated proxies start at 1 IP: `GET /billing/dedicated/countries`, `POST /billing/dedicated/quote`, `POST /billing/dedicated/order` (the `Idempotency-Key` header and `expected_total_cents` are required). ## Scopes - `catalog:read` — products, countries, prices - `services:read` — services, IP lists, credentials, IP whitelisting - `services:write` — credentials, IP whitelisting, IP refresh, sticky sessions, cancellation - `usage:read` — usage statistics - `wallet:read` — balance, ledger, invoice PDFs - `wallet:deposit` — balance top-ups - `wallet:spend` — paying from the balance: purchases, renewals, add-ons, extra IPs, paid IP refreshes - `tickets:read`, `tickets:write` — support tickets - `customer:read`, `customer:write` — profile - `webhooks` — webhook endpoints and deliveries ## Rules for agents - Show the user a quote and get confirmation before any purchase. - Send an `idempotency_key` with paid operations that may be retried. - Don't print the API key in replies or save it to project files. - If a scope is missing, tell the user which scope is required instead of working around it. --- # PapaProxy.net API — API reference Page: https://papaproxy.net/developers/reference/ Last updated: November 9, 2026 The PapaProxy.net REST API exposes the same operations as the Dashboard: price quotes, orders, IP top-ups, proxy credentials, IP whitelisting, rotating-proxy sessions, wallet, support tickets, and webhooks. Base URL: `https://papaproxy.net/account_new` ## Authentication Pass an API key as a bearer token: ``` Authorization: Bearer ``` Create and revoke keys in the Dashboard under Settings → API keys (https://papaproxy.net/account_new/settings#api-keys). The `/api/v1/customers/me/api-keys` endpoints work only in a signed-in Dashboard session: an API key can't list, create, or revoke keys, so a leaked key can't be used to issue new ones. Each key has an explicit set of scopes. A request made with a key that lacks the scope an endpoint requires fails with `403`; a missing, malformed, revoked, or expired key fails with `401`. A few endpoints work without a key: `GET /api/v1/billing/countries`, `GET /api/v1/billing/composite/pricing-info`, `POST /api/v1/billing/composite/auto-plan`, `POST /api/v1/billing/composite/quote`, `GET /api/v1/billing/dedicated/countries`, and `POST /api/v1/billing/dedicated/quote`. ## Conventions - Money is always an integer number of cents (USD). Fields ending in `_millicents` are thousandths of a cent: `220000` millicents is $2.20. - Countries in requests are ISO 3166-1 alpha-2 codes (`DE`, `GB`, `US`). Proxy-package endpoints also accept the numeric country ID from `GET /api/v1/billing/countries`; dedicated-proxy endpoints accept ISO codes only. - In proxy-package responses, `country` is the numeric country ID and `country_code` is the ISO code. - Timestamps are ISO 8601 in UTC. - Requests that charge your wallet confirm the price you showed the user (`expected_price_cents` or `expected_total_cents`). If the price is missing or has changed, the request fails and nothing is charged. - Endpoints that charge your wallet take an idempotency key; see Idempotency below. ## Order minimums - **Proxy packages** (datacenter and ISP proxies, `/api/v1/billing/composite/*`): at least 100 IPs per order and at least 10 IPs per country. Adding IPs to a package follows the same rule: at least 100 IPs per request, at least 10 per country. `GET /api/v1/billing/composite/pricing-info` returns these values as `min_ips_per_order` and `min_ips_per_segment`. - **Dedicated proxies** (`/api/v1/billing/dedicated/*`): from 1 IP per order, and from 1 IP when you add IPs to a dedicated service. - An order, auto-plan, or quote with fewer than 100 IPs in total fails with `422`. A quote with a country below 10 IPs still returns `200`: that country is listed in `errors` and left out of `segments` and the total. Always check `errors` before you place the order. ## Errors Every error response is JSON with a `detail` field. Check the HTTP status first, then read `detail`, which is either a string or an object: ``` {"detail": "Invalid or revoked API key."} {"detail": {"error": "PRICE_CHANGED", "expected_price_cents": 1, "actual_price_cents": 3}} ``` When `detail` is an object, the machine-readable code is in `detail.error` (proxy-package orders, add-ons, service cancellation) or in `detail.code` (dedicated proxies, IP refresh). Schema validation errors return `422` with `{"detail": "Validation error"}` and don't name the field, so compare the request with the field table of the endpoint. - `400` — the request can't be completed in the current state: an unknown add-on type or a missing add-on parameter, a reply to a closed ticket, or insufficient balance for a renewal. - `401` — the API key is missing, malformed, revoked, or expired, or the account is inactive. The response includes `WWW-Authenticate: Bearer`. - `402` — insufficient wallet balance when adding IPs or buying an add-on. Nothing is charged. - `403` — the API key lacks the required scope, or a top-up needs identity verification. - `404` — the resource doesn't exist or doesn't belong to your account. - `409` — the price wasn't confirmed or has changed, stock ran out, or a cancellation cooldown is active. Nothing is charged. - `422` — a validation error or a business rule: order minimums, a required whitelisted IP, an invalid promo code. - `429` — rate limit exceeded; see Rate limits. - `501` — the operation isn't supported for this service. - `5xx` — a temporary server error (`500` returns `{"detail": "Internal server error"}`; `503` means the service is briefly unavailable). Retry after a few seconds with backoff, using the same idempotency key. The MCP server doesn't return HTTP status codes to your agent: a failed tool call comes back as error text with a short code and an explanation, for example `insufficient_pool: … Nothing was charged.` ## Rate limits - Each account can make up to 60 requests per minute (a rolling window). All API keys of the account and the MCP server share this limit. - Requests are also limited per source IP address, including requests without a key, and short bursts are limited separately. Spread requests evenly rather than sending them in bursts. - A limited request returns `429` with `{"detail": "Too Many Requests"}`. Wait and retry with exponential backoff (for example, 2, 4, 8 seconds); nothing is charged for a rejected request. - Each response includes an `X-Request-Id` header. Include it when you contact support about a specific request. ## Testing There's no sandbox or test mode: every order, top-up, renewal, and add-on charges your real wallet balance. To build and test an integration without spending money: - Use quotes (`POST /api/v1/billing/composite/quote`, `POST /api/v1/billing/dedicated/quote`), which never charge and work without an API key. - Test webhook handling with `POST /api/v1/customer/webhooks/{endpoint_id}/test`, which sends a `ping` event. - Give a key only the scopes it needs, and keep keys that can spend money out of test environments. ## Webhooks Webhook deliveries are `POST` requests with a compact JSON body: ``` {"id": "3f1c2a9e-8b7d-4e6f-9a01-2c5d7e8f9b10", "type": "service.renewed", "created_at": "2026-11-09T13:16:07.181118Z", "data": {…}} ``` `id` is the event ID, `type` is the event type, `created_at` is in UTC, and `data` depends on the event type. Each delivery carries these headers: - `X-FB-Signature` — `v1=` followed by the hex-encoded HMAC-SHA256 signature. - `X-FB-Timestamp` — the Unix time (seconds) when the delivery was signed. - `X-FB-Event-Id` — the event ID. It stays the same when a delivery is retried or redelivered. - `X-FB-Event-Type` — the event type. To verify a delivery, compute HMAC-SHA256 over `{X-FB-Timestamp}.{raw body}` with the endpoint's full signing secret, including the `whsec_` prefix, as the key, and compare the hex digest with the value after `v1=`. Use the raw request bytes, not re-serialized JSON: ``` import hashlib, hmac, time def verify(secret: str, body: bytes, timestamp: str, signature: str) -> bool: if abs(time.time() - int(timestamp)) > 300: return False digest = hmac.new(secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest() return hmac.compare_digest("v1=" + digest, signature) ``` - Reject deliveries whose timestamp is more than 5 minutes from your server's time, and skip event IDs you've already processed. Retries are signed again with a new timestamp. - Respond with any `2xx` status within 10 seconds. Other statuses, timeouts, and redirects (`3xx` isn't followed) count as failures. - A failed delivery is retried 5 more times: after 1 minute, 5 minutes, 30 minutes, 2 hours, and 6 hours. After that it's marked as failed; you can send it again with the redeliver endpoint. - After 20 consecutive failed deliveries, the endpoint is disabled automatically and `disabled_reason` explains why. A successful delivery resets the counter. - The URL must be a public `https` address; private and internal addresses are rejected. - Deliveries don't come from a fixed set of IP addresses, so verify the signature instead of whitelisting sender IPs. - Event types: `payment.succeeded`, `payment.failed`, `deposit.completed`, `service.provisioned`, `service.renewed`, `service.expiring`, `service.suspended`, `service.terminated`, and `wallet.balance_low`. `GET /api/v1/customer/webhooks/events` returns the current list. `ping` is sent only by the test endpoint and can't be subscribed to. ## Pagination List endpoints return results in pages. Pass `offset` (how many items to skip) and `limit` (page size: 50 by default) as query parameters, and keep increasing `offset` by `limit` until a page comes back with fewer items than `limit`. - `GET /api/v1/customer/wallet/ledger` — List wallet transactions (`limit` up to 200) - `GET /api/v1/customer/catalog` — List products (`limit` up to 100) - `GET /api/v1/customer/services` — List your services (`limit` up to 100) - `GET /api/v1/customer/tickets` — List support tickets ## Idempotency The endpoints below take an idempotency key — `idempotency_key` in the request body unless noted otherwise. If a request times out or the connection drops, retry it with the same key and the same body: you get the result of the original request, and you aren't charged twice. Use a new key for each new purchase. Without a key, a repeated request is processed again. - `POST /api/v1/billing/composite/order` — Place an order - `POST /api/v1/billing/services/{service_id}/add-ips` — Add IPs to a service (required) - `POST /api/v1/billing/services/{service_id}/addons` — Buy an add-on (required) - `POST /api/v1/customer/wallet/deposit/init` — Top up your wallet - `POST /api/v1/customer/services/{service_id}/renew` — Renew a service - `POST /api/v1/customer/services/{service_id}/proxy/refresh` — Refresh proxy IPs (required) - `POST /api/v1/billing/dedicated/order` — Order dedicated proxies (in the `Idempotency-Key` header, required) ## Endpoints 62 operations in 12 groups. ### [Catalog and pricing](https://papaproxy.net/developers/reference/catalog-and-pricing.md) - `GET /api/v1/billing/countries` — List available countries - `GET /api/v1/billing/composite/pricing-info` — Get pricing terms - `POST /api/v1/billing/composite/auto-plan` — Distribute IPs across countries - `POST /api/v1/billing/composite/quote` — Get a price quote - `GET /api/v1/customer/catalog` — List products - `GET /api/v1/customer/catalog/{product_id}` — Get a product ### [Orders](https://papaproxy.net/developers/reference/orders.md) - `POST /api/v1/billing/composite/order` — Place an order - `POST /api/v1/billing/services/{service_id}/add-ips/quote` — Quote additional IPs - `POST /api/v1/billing/services/{service_id}/add-ips` — Add IPs to a service ### [Dedicated proxies](https://papaproxy.net/developers/reference/dedicated-proxies.md) - `GET /api/v1/billing/dedicated/countries` — List dedicated-proxy countries - `POST /api/v1/billing/dedicated/quote` — Get a dedicated-proxy quote - `POST /api/v1/billing/dedicated/order` — Order dedicated proxies ### [Services](https://papaproxy.net/developers/reference/services.md) - `GET /api/v1/customer/services` — List your services - `POST /api/v1/customer/services` — Buy a service - `GET /api/v1/customer/services/{service_id}` — Get a service - `POST /api/v1/customer/services/{service_id}/renew` — Renew a service - `POST /api/v1/customer/services/{service_id}/cancel` — Cancel a service - `GET /api/v1/customer/services/{service_id}/usage` — Get usage statistics ### [Proxy access](https://papaproxy.net/developers/reference/proxy-access.md) - `GET /api/v1/customer/services/{service_id}/proxy/ips` — List proxy IPs - `GET /api/v1/customer/services/{service_id}/proxy/auth` — Get proxy credentials - `PUT /api/v1/customer/services/{service_id}/proxy/auth` — Change proxy credentials - `GET /api/v1/customer/services/{service_id}/proxy/whitelist` — List whitelisted IPs - `POST /api/v1/customer/services/{service_id}/proxy/whitelist` — Add a whitelisted IP - `DELETE /api/v1/customer/services/{service_id}/proxy/whitelist` — Remove a whitelisted IP - `GET /api/v1/customer/services/{service_id}/proxy/refresh-status` — Check IP refresh cost - `POST /api/v1/customer/services/{service_id}/proxy/refresh` — Refresh proxy IPs ### [Rotating sessions](https://papaproxy.net/developers/reference/rotating-sessions.md) - `GET /api/v1/customer/services/{service_id}/proxy/config` — Get rotating gateway settings - `GET /api/v1/customer/services/{service_id}/proxy/sticky/sessions` — List sticky sessions - `POST /api/v1/customer/services/{service_id}/proxy/sticky/sessions/{sid}/rotate` — Rotate a sticky session - `DELETE /api/v1/customer/services/{service_id}/proxy/sticky/sessions/{sid}` — End a sticky session - `POST /api/v1/customer/services/{service_id}/proxy/sticky/purge` — End all sticky sessions - `GET /api/v1/customer/services/{service_id}/proxy/sticky/mode` — Get session mode - `PUT /api/v1/customer/services/{service_id}/proxy/sticky/mode` — Set session mode - `GET /api/v1/customer/services/{service_id}/proxy/sticky/ttl` — Get sticky session duration - `PUT /api/v1/customer/services/{service_id}/proxy/sticky/ttl` — Set sticky session duration - `DELETE /api/v1/customer/services/{service_id}/proxy/sticky/ttl` — Reset sticky session duration ### [Add-ons](https://papaproxy.net/developers/reference/add-ons.md) - `GET /api/v1/billing/services/{service_id}/addons/available` — List available add-ons - `GET /api/v1/billing/services/{service_id}/addons` — List active add-ons - `POST /api/v1/billing/services/{service_id}/addons` — Buy an add-on - `DELETE /api/v1/billing/services/{service_id}/addons/{addon_id}` — Cancel an add-on ### [Wallet](https://papaproxy.net/developers/reference/wallet.md) - `GET /api/v1/customer/wallet/balance` — Get wallet balance - `GET /api/v1/customer/wallet/ledger` — List wallet transactions - `POST /api/v1/customer/wallet/deposit/init` — Top up your wallet - `GET /api/v1/customer/invoices/{invoice_id}/pdf` — Download an invoice (PDF) ### [Account](https://papaproxy.net/developers/reference/account.md) - `GET /api/v1/customer/me` — Get your profile - `PATCH /api/v1/customer/me` — Update your profile ### [API keys](https://papaproxy.net/developers/reference/api-keys.md) - `GET /api/v1/customers/me/api-keys` — List API keys - `POST /api/v1/customers/me/api-keys` — Create an API key - `DELETE /api/v1/customers/me/api-keys/{key_id}` — Revoke an API key ### [Support tickets](https://papaproxy.net/developers/reference/support-tickets.md) - `GET /api/v1/customer/tickets` — List support tickets - `POST /api/v1/customer/tickets` — Open a support ticket - `GET /api/v1/customer/tickets/{ticket_id}` — Get a support ticket - `POST /api/v1/customer/tickets/{ticket_id}/reply` — Reply to a support ticket ### [Webhooks](https://papaproxy.net/developers/reference/webhooks.md) - `GET /api/v1/customer/webhooks/events` — List webhook event types - `GET /api/v1/customer/webhooks` — List webhook endpoints - `POST /api/v1/customer/webhooks` — Create a webhook endpoint - `GET /api/v1/customer/webhooks/{endpoint_id}` — Get a webhook endpoint - `PATCH /api/v1/customer/webhooks/{endpoint_id}` — Update a webhook endpoint - `DELETE /api/v1/customer/webhooks/{endpoint_id}` — Delete a webhook endpoint - `GET /api/v1/customer/webhooks/{endpoint_id}/deliveries` — List webhook deliveries - `POST /api/v1/customer/webhooks/{endpoint_id}/test` — Send a test event - `POST /api/v1/customer/webhooks/deliveries/{delivery_id}/redeliver` — Redeliver an event ## More resources - Developer guide and MCP server setup: https://papaproxy.net/developers.php - OpenAPI 3.1 specification: https://papaproxy.net/openapi.json - All developer docs in one file: https://papaproxy.net/llms-full.txt --- # Catalog and pricing — PapaProxy.net API reference Page: https://papaproxy.net/developers/reference/catalog-and-pricing.php Last updated: November 9, 2026 Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/ ### List available countries `GET /api/v1/billing/countries` - Access: No API key required List countries with their ISO 3166-1 alpha-2 codes, numeric IDs, and the number of IPs currently available for proxy packages. No API key required. Pass the `code` straight to `segments[].country` in quote and order requests. Countries are sorted by available IPs, most first. Defaults: `product_kind=proxy_datacenter`, `geo_source=maxmind` (use the same `geo_source` in quote and order requests). For dedicated proxies, use `GET /api/v1/billing/dedicated/countries`. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `product_kind (query)` | string, nullable | no | Product: proxy_datacenter or proxy_isp | | `geo_source (query)` | string, nullable | no | Geo source the country tags are scoped to: maxmind or ripe | Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/billing/countries" ``` Python: ```python import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/billing/countries", timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/countries", { method: "GET", }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'GET', CURLOPT_RETURNTRANSFER => true, ]); $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" ) func main() { req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/billing/countries", nil) if err != nil { panic(err) } 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` — Countries you can order. Example response: ```json { "geo_source": "maxmind", "product_kind": "proxy_datacenter", "countries": [ { "code": "US", "name": "United States", "tag_id": 197148, "available": 32452 }, { "code": "RU", "name": "Russia", "tag_id": 197146, "available": 15616 } ] } ``` | Field | Type | Required | Description | |---|---|---|---| | `geo_source` | string | yes | Geo source the country list is scoped to (maxmind/ripe) | | `product_kind` | string | yes | Product kind the availability counts are computed for | | `countries` | array of object | no | Available countries, most stock first | | `countries[].code` | string | yes | ISO 3166-1 alpha-2 code (e.g. 'DE', 'GB') | | `countries[].name` | string | yes | Country name (e.g. 'Germany') | | `countries[].tag_id` | integer | yes | Numeric country ID (the ISO code is accepted everywhere this ID is). | | `countries[].available` | integer | yes | IPs currently available in this country | - `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 pricing terms `GET /api/v1/billing/composite/pricing-info` - Access: No API key required Volume-discount tiers, term discounts, order minimums, included concurrent connections, and add-on prices for proxy packages — everything a calculator needs to show discount thresholds before requesting a quote. No API key required. `min_ips_per_order` (100) and `min_ips_per_segment` (10) are the package minimums; they also apply when you add IPs to a package. Dedicated proxies start at 1 IP; price them with `POST /api/v1/billing/dedicated/quote`. The figures are for display only; quote and order responses are authoritative. Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/billing/composite/pricing-info" ``` Python: ```python import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/billing/composite/pricing-info", timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/composite/pricing-info", { method: "GET", }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'GET', CURLOPT_RETURNTRANSFER => true, ]); $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" ) func main() { req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/billing/composite/pricing-info", nil) if err != nil { panic(err) } 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` — Pricing terms for display: volume-discount tiers, term discounts, order minimums, included concurrent connections, and add-on prices. Example response: ```json { "volume_tiers": [ { "min_ips": 0, "price_millicents_per_ip": 19000, "label": null }, { "min_ips": 100, "price_millicents_per_ip": 19000, "label": null } ], "period_discounts": {}, "min_ips_per_segment": 10, "min_ips_per_order": 100, "conn_cap_tiers": [ { "min_ips": 0, "max_ips": 800, "flat_cap": 1500, "multiplier": null }, { "min_ips": 800, "max_ips": 5000, "flat_cap": null, "multiplier": 3 } ], "whitelist_free_quota": 5, "extra_whitelist_ip_price_cents": 100, "free_refresh_days": 8, "trial": { "enabled": true, "period_days": 2, "tariff_coefficient": 2.0, "registration_window_enabled": false, "registration_window_days": 30, "max_per_month": 3 } } ``` | Field | Type | Required | Description | |---|---|---|---| | `volume_tiers` | array of object | yes | Volume-discount tiers, ascending by `min_ips`. | | `volume_tiers[].min_ips` | integer | yes | Total-IP threshold at which this tier applies · Minimum: 0 | | `volume_tiers[].price_millicents_per_ip` | integer | yes | Per-IP price in millicents (1/1000 of a cent) · Minimum: 0 | | `volume_tiers[].label` | string, nullable | no | Human-readable tier label. | | `period_discounts` | object | yes | Period-months (string key) -> whole-percentage discount | | `min_ips_per_segment` | integer | yes | Minimum IP count for a single segment to be accepted · Minimum: 1 | | `min_ips_per_order` | integer | yes | Minimum total IP count (summed across segments) for an order · Minimum: 1 | | `conn_cap_tiers` | array of object | no | Included concurrent connections by IP count. | | `conn_cap_tiers[].min_ips` | integer | yes | Inclusive lower bound of this bucket's IP range · Minimum: 0 | | `conn_cap_tiers[].max_ips` | integer, nullable | no | Exclusive upper bound; null = no upper bound (last bucket) | | `conn_cap_tiers[].flat_cap` | integer, nullable | no | Flat connection cap for this bucket | | `conn_cap_tiers[].multiplier` | integer, nullable | no | Per-IP connection multiplier for this bucket | | `whitelist_free_quota` | integer | no | Whitelist IPs included free with each service. · Default: `0` | | `extra_whitelist_ip_price_cents` | integer | no | Monthly price per whitelist IP beyond the free allowance, in cents. · Default: `0` | | `free_refresh_days` | integer | no | Interval, in days, between free scheduled IP list refreshes. · Default: `0` | | `trial` | object | no | Paid 48-hour trial settings: availability, length in days, and price coefficient. | | `trial.enabled` | boolean | no | Default: `true` | | `trial.period_days` | integer | no | Term length in days. · Minimum: 1 · Default: `2` | | `trial.tariff_coefficient` | number | no | Default: `2` | | `trial.registration_window_enabled` | boolean | no | Default: `false` | | `trial.registration_window_days` | integer | no | Minimum: 1 · Default: `30` | | `trial.max_per_month` | integer | no | Minimum: 0 · Default: `3` | - `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"} ``` ### Distribute IPs across countries `POST /api/v1/billing/composite/auto-plan` - Access: No API key required Spread a total IP count across the countries that are currently in stock (the “Country mix” option). Optionally exclude countries with `excluded_countries` (ISO codes). No API key required and no charge. `total_ip` must be at least 100 (the package minimum); a smaller value returns `422`. Pass the returned `segments` to the quote endpoint for pricing and to the order endpoint to purchase. Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `total_ip` | integer | yes | Total number of IPs to distribute. At least 100. · Minimum: 1 · Maximum: 1000000 | | `product_kind` | string | no | Product: `proxy_datacenter` or `proxy_isp`. Dedicated proxies are priced and ordered through `/api/v1/billing/dedicated/*`. · Default: `"proxy_datacenter"` | | `geo_source` | string | no | Geo source for country lookups · One of: `maxmind`, `ripe` · Default: `"maxmind"` | | `excluded_countries` | array of integer or string | no | Countries to leave out, as ISO 3166-1 alpha-2 codes. | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/billing/composite/auto-plan" \ -H "Content-Type: application/json" \ -d '{"total_ip": 300}' ``` Python: ```python import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/billing/composite/auto-plan", json={ "total_ip": 300 }, timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/composite/auto-plan", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ "total_ip": 300 }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'total_ip' => 300 ]), ]); $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" "strings" ) func main() { req, err := http.NewRequest("POST", "https://papaproxy.net/account_new/api/v1/billing/composite/auto-plan", strings.NewReader(`{"total_ip": 300}`)) if err != nil { panic(err) } 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` — Proposed distribution of IPs by country. Example response: ```json { "snapshot_version": "5c7f1a22-bcbf-5cf5-845f-92443fc4d544", "segments": [ { "country": 197148, "country_code": "US", "count": 148 }, { "country": 197146, "country_code": "RU", "count": 71 } ], "total_ip": 300, "countries_used": 4, "subnet_diversity": null } ``` | Field | Type | Required | Description | |---|---|---|---| | `snapshot_version` | string (uuid) | yes | Version of the availability data used for this response. | | `segments` | array of object | no | Proposed per-country distribution, summing to total_ip | | `segments[].country` | integer | yes | Numeric country ID. The ISO code is in `country_code`. | | `segments[].country_code` | string, nullable | no | ISO 3166-1 alpha-2 country code (e.g. "DE"). | | `segments[].count` | integer | yes | Number of IPs allocated to this country · Minimum: 1 | | `total_ip` | integer | yes | Total IP count across all segments (echoes the request) | | `countries_used` | integer | yes | Number of distinct countries in the plan | | `subnet_diversity` | integer, nullable | no | Number of distinct /24 subnets in the proposed allocation. Available for ISP proxies only; null for other products. | - `422` — Validation error, or `total_ip` is below 100. 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 price quote `POST /api/v1/billing/composite/quote` - Access: API key optional Price a set of segments (`[{country, count}]`) for a proxy package and term. The quote is non-binding (`binding=false`). - The total must be at least 100 IPs; otherwise the response is `422`. - A segment that can't be fulfilled — fewer than 10 IPs, or more IPs than are in stock — doesn't fail the request: it's listed in `errors` and left out of `segments` and the total. Always check `errors` before you order. - An invalid `promo_code` returns `422` (`Invalid promo code.`). The API key is optional. With a key from an account that has a partner discount, the response also includes `retail_*` and `partner_discount_*` fields; otherwise those fields are null. Pricing: the per-IP rate depends on the total IP count across all segments (volume tiers); term discounts apply on top. See `GET /api/v1/billing/composite/pricing-info` for the tiers. Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `segments` | array of object | yes | One or more segments forming the mix · Min items: 1 | | `segments[].country` | integer or string | yes | ISO 3166-1 alpha-2 country code ("DE", "GB"). A numeric country ID from `GET /api/v1/billing/countries` is also accepted. | | `segments[].count` | integer | yes | Number of IPs in this country. At least 10; the order total must be at least 100. · Minimum: 1 · Maximum: 100000 | | `product_kind` | string | no | Product: `proxy_datacenter` or `proxy_isp`. Dedicated proxies are priced and ordered through `/api/v1/billing/dedicated/*`. · Default: `"proxy_datacenter"` | | `period_months` | integer | no | Term length in months (1, 3, 6, or 12). Longer terms get a discount. Default: 1. | | `geo_source` | string | no | Geo source for country lookups: maxmind (geolocation) or ripe (RIR allocation). Default maxmind. · One of: `maxmind`, `ripe` | | `promo_code` | string, nullable | no | Optional promo code, applied after volume and term discounts. · Max length: 64 | | `trial` | boolean | no | Price the paid 48-hour trial instead of a full term. · Default: `false` | | `extra_whitelist_count` | integer | no | Paid whitelist IPs beyond the free allowance. · Minimum: 0 · Maximum: 10000 · Default: `0` | | `udp_unlock` | boolean | no | Include the UDP add-on (a percentage of the base price). · Default: `false` | | `extra_connections_count` | integer | no | Paid concurrent connections beyond those included, priced into the quote. For an existing service, buy the `extra_connections` add-on instead. · Minimum: 0 · Maximum: 10000 · Default: `0` | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/billing/composite/quote" \ -H "Content-Type: application/json" \ -d '{"segments": [{"country": "US", "count": 100}], "period_months": 1}' ``` Python: ```python import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/billing/composite/quote", json={ "segments": [ { "country": "US", "count": 100 } ], "period_months": 1 }, timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/composite/quote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ "segments": [ { "country": "US", "count": 100 } ], "period_months": 1 }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'segments' => [[ 'country' => 'US', 'count' => 100 ]], 'period_months' => 1 ]), ]); $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" "strings" ) func main() { req, err := http.NewRequest("POST", "https://papaproxy.net/account_new/api/v1/billing/composite/quote", strings.NewReader(`{"segments": [{"country": "US", "count": 100}], "period_months": 1}`)) if err != nil { panic(err) } 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` — Price quote. Check `errors` for segments that were left out. Example response: ```json { "snapshot_version": "5c7f1a22-bcbf-5cf5-845f-92443fc4d544", "built_at": "2026-10-30T04:00:18.702558Z", "binding": false, "segments": [ { "country": 197148, "country_code": "US", "count": 100, "unit_base_cents": 19, "type_multiplier": 1.0, "geo_multiplier": 1.0, "segment_price_cents": 1900, "available": true } ], "errors": [], "total_ip": 100, "total_price_cents": 1900, "unit_base_cents": 19, "period_months": 1, "discount_pct": 0, "volume_discount_pct": 0, "monthly_price_cents": 1900, "retail_total_price_cents": null, "retail_monthly_price_cents": null, "partner_discount_percent": null, "partner_discount_cents": null, "promo_code_applied": null, "promo_discount_cents": 0, "total_before_promo_cents": null, "trial": false, "trial_period_days": null, "trial_coefficient": null, "addon_monthly_cents": 0, "addon_breakdown": [], "trial_price_cents": null, "extra_whitelist_count": 0, "extra_connections_count": 0, "udp_unlock": false } ``` | Field | Type | Required | Description | |---|---|---|---| | `snapshot_version` | string (uuid) | yes | Version of the availability data used for this quote. | | `built_at` | string (date-time), nullable | no | When the availability data was last updated (UTC). | | `binding` | boolean | no | Always false — quotation is non-binding · Default: `false` | | `segments` | array of object | no | Per-segment pricing breakdown | | `segments[].country` | integer | yes | Numeric country ID. The ISO code is in `country_code`. | | `segments[].country_code` | string, nullable | no | ISO 3166-1 alpha-2 country code (e.g. "DE"). | | `segments[].count` | integer | yes | | | `segments[].unit_base_cents` | integer | yes | Volume-tier base unit price in integer cents per IP | | `segments[].type_multiplier` | number | yes | Product multiplier applied to the base rate. | | `segments[].geo_multiplier` | number | yes | Country multiplier (default 1.0). | | `segments[].segment_price_cents` | integer | yes | Segment price: count × base rate × product multiplier × country multiplier, rounded up to whole cents. | | `segments[].available` | boolean | yes | Whether enough IPs are available for this segment. | | `errors` | array of object | no | Errors for unavailable/invalid segments | | `errors[].country` | integer | yes | Numeric country ID of the segment that was left out. | | `errors[].message` | string | yes | | | `total_ip` | integer | no | Total IP count across all valid segments · Default: `0` | | `total_price_cents` | integer | no | Discounted total charged for the full period in integer cents. Equals monthly_price_cents x period_months x (1 - discount). · Default: `0` | | `unit_base_cents` | integer | no | Volume-tier base unit price applied (cents per IP) · Default: `0` | | `period_months` | integer | no | Billing period length in months (1/3/6/12) · Default: `1` | | `discount_pct` | integer | no | Term discount as a whole percentage. The volume discount is reported in `volume_discount_pct`. · Default: `0` | | `volume_discount_pct` | integer | no | Volume discount as a whole percentage, for display. Term discount is reported separately in `discount_pct`; both are already included in `total_price_cents`. · Default: `0` | | `monthly_price_cents` | integer | no | Undiscounted monthly total (sum of segment prices) in integer cents · Default: `0` | | `retail_total_price_cents` | integer, nullable | no | Total before the partner discount, in cents. Null unless a partner discount applies. | | `retail_monthly_price_cents` | integer, nullable | no | Monthly price before the partner discount, in cents. Null unless a partner discount applies. | | `partner_discount_percent` | integer, nullable | no | Partner discount percentage, when it applies. | | `partner_discount_cents` | integer, nullable | no | Partner discount amount in cents, when it applies. | | `promo_code_applied` | string, nullable | no | Promo code applied to this quote, if any. | | `promo_discount_cents` | integer | no | Absolute promo discount in integer cents applied to total_price_cents. · Minimum: 0 · Default: `0` | | `total_before_promo_cents` | integer, nullable | no | Pre-promo total in integer cents when a promo was applied; null when no promo. | | `trial` | boolean | no | True when the quote is for the paid trial. · Default: `false` | | `trial_period_days` | integer, nullable | no | Trial length in days; null for regular quotes. | | `trial_coefficient` | number, nullable | no | Price coefficient of the trial; null for regular quotes. | | `addon_monthly_cents` | integer | no | Monthly cents from pre-purchase add-ons (whitelist + UDP + connections). · Minimum: 0 · Default: `0` | | `addon_breakdown` | array of object | no | Per-addon monthly line-items. Sum of monthly_cents equals addon_monthly_cents. | | `addon_breakdown[].addon_type` | string | yes | Add-on type. · One of: `extra_whitelist_ip`, `udp_unlock`, `extra_connections` | | `addon_breakdown[].quantity` | integer | yes | Number of units (IPs or connections; 1 for UDP). · Minimum: 0 | | `addon_breakdown[].unit_price_cents` | integer | yes | Per-unit monthly cents · Minimum: 0 | | `addon_breakdown[].monthly_cents` | integer | yes | Monthly cost: quantity × unit price (UDP is a percentage of the base price). · Minimum: 0 | | `trial_price_cents` | integer, nullable | no | Trial price in cents (before promo); null for regular quotes. | | `extra_whitelist_count` | integer | no | Paid whitelist slots included in this quote. · Minimum: 0 · Default: `0` | | `extra_connections_count` | integer | no | Pre-purchase extra concurrent connections included in this quote. · Minimum: 0 · Default: `0` | | `udp_unlock` | boolean | no | Whether UDP unlock was included in this quote. · Default: `false` | - `422` — Validation error, fewer than 100 IPs in total, or an invalid promo code. Example: ```json {"detail": "Invalid promo code."} ``` - `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 products `GET /api/v1/customer/catalog` - Access: API key required · scope `catalog:read` The product catalog with your prices. 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/catalog" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/customer/catalog", 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/catalog", { 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 '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/catalog", 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 product list. Example response: ```json { "products": [ { "id": "5234db61-2f19-5aa0-8e64-faa72f7d4b76", "kind": "proxy_rotating", "billing_model": "metered_requests", "name": "Starter 2.5M", "description": "2.5 million requests over 30 days, about 83,000 a day. New datacenter IPv4 from a 100,000+ pool on every request; sticky sessions keep one IP when you need it. Unlimited bandwidth.", "params": { "ip_count": null, "geo": null, "protocol": null, "rate_limit_rps": null, "sticky": true, "data_limit_bytes": null, "traffic_gb": null, "pool_class": "datacenter", "devices": null, "period_days": 30, "request_package_size": 2500000 }, "price_cents": 4900, "price_unit": "period", "price_book_id": "1f4229e5-ac7e-50ae-bc7c-2680e501a91f", "is_active": true, "features": [] }, { "id": "79001ddf-8570-583a-9045-0511848550e4", "kind": "proxy_rotating", "billing_model": "metered_requests", "name": "Standard 5M", "description": "5 million requests over 30 days, about 167,000 a day. New datacenter IPv4 from a 100,000+ pool on every request; sticky sessions keep one IP when you need it. Unlimited bandwidth.", "params": { "ip_count": null, "geo": null, "protocol": null, "rate_limit_rps": null, "sticky": true, "data_limit_bytes": null, "traffic_gb": null, "pool_class": "datacenter", "devices": null, "period_days": 30, "request_package_size": 5000000 }, "price_cents": 7900, "price_unit": "period", "price_book_id": "1f4229e5-ac7e-50ae-bc7c-2680e501a91f", "is_active": true, "features": [] } ], "total": 6, "offset": 0, "limit": 2, "price_book_kind": "retail" } ``` | Field | Type | Required | Description | |---|---|---|---| | `products` | array of object | yes | | | `products[].id` | string (uuid) | yes | Unique ID. | | `products[].kind` | string | yes | Product type, for example `proxy_rotating`. | | `products[].billing_model` | string | yes | | | `products[].name` | string | yes | | | `products[].description` | string, nullable | yes | | | `products[].params` | object, nullable | yes | Product parameters; they vary by product type (for example IP count, locations, term, request allowance, or session settings). | | `products[].params.ip_count` | integer, nullable | no | Number of IPs. | | `products[].params.geo` | array of string, nullable | no | | | `products[].params.protocol` | string, nullable | no | | | `products[].params.rate_limit_rps` | integer, nullable | no | | | `products[].params.sticky` | boolean, nullable | no | | | `products[].params.data_limit_bytes` | integer, nullable | no | Data allowance in bytes, for products with a data allowance. | | `products[].params.traffic_gb` | number, nullable | no | Data allowance in GB, for products with a data allowance. | | `products[].params.pool_class` | string, nullable | no | IP pool type of the product. · One of: `datacenter`, `isp` | | `products[].params.devices` | integer, nullable | no | | | `products[].params.period_days` | integer, nullable | no | Term length in days. | | `products[].params.request_package_size` | integer, nullable | no | Prepaid request count for rotating proxies (for example 2,500,000). | | `products[].price_cents` | integer, nullable | no | Resolved retail price in cents; null if none | | `products[].price_unit` | string, nullable | no | | | `products[].price_book_id` | string (uuid), nullable | no | ID of the price list the price comes from. | | `products[].is_active` | boolean | yes | Whether it's active. | | `products[].features` | array of string | no | Product features. | | `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` | | `price_book_kind` | string | no | One of: `retail`, `wholesale` · Default: `"retail"` | - `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 `catalog:read`. Example: ```json {"detail": "API key is missing required scope: catalog: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"} ``` ### Get a product `GET /api/v1/customer/catalog/{product_id}` - Access: API key required · scope `catalog:read` Product details with your price. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `product_id (path)` | string (uuid) | yes | ID of the product. | Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/catalog/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/catalog/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/catalog/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 '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/catalog/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 catalog product. Example response: ```json { "id": "5234db61-2f19-5aa0-8e64-faa72f7d4b76", "kind": "proxy_rotating", "billing_model": "metered_requests", "name": "Starter 2.5M", "description": "2.5 million requests over 30 days, about 83,000 a day. New datacenter IPv4 from a 100,000+ pool on every request; sticky sessions keep one IP when you need it. Unlimited bandwidth.", "params": { "ip_count": null, "geo": null, "protocol": null, "rate_limit_rps": null, "sticky": true, "data_limit_bytes": null, "traffic_gb": null, "pool_class": "datacenter", "devices": null, "period_days": 30, "request_package_size": 2500000 }, "price_cents": 4900, "price_unit": "period", "price_book_id": "1f4229e5-ac7e-50ae-bc7c-2680e501a91f", "is_active": true, "features": [] } ``` | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Unique ID. | | `kind` | string | yes | Product type, for example `proxy_rotating`. | | `billing_model` | string | yes | | | `name` | string | yes | | | `description` | string, nullable | yes | | | `params` | object, nullable | yes | Product parameters; they vary by product type (for example IP count, locations, term, request allowance, or session settings). | | `params.ip_count` | integer, nullable | no | Number of IPs. | | `params.geo` | array of string, nullable | no | | | `params.protocol` | string, nullable | no | | | `params.rate_limit_rps` | integer, nullable | no | | | `params.sticky` | boolean, nullable | no | | | `params.data_limit_bytes` | integer, nullable | no | Data allowance in bytes, for products with a data allowance. | | `params.traffic_gb` | number, nullable | no | Data allowance in GB, for products with a data allowance. | | `params.pool_class` | string, nullable | no | IP pool type of the product. · One of: `datacenter`, `isp` | | `params.devices` | integer, nullable | no | | | `params.period_days` | integer, nullable | no | Term length in days. | | `params.request_package_size` | integer, nullable | no | Prepaid request count for rotating proxies (for example 2,500,000). | | `price_cents` | integer, nullable | no | Resolved retail price in cents; null if none | | `price_unit` | string, nullable | no | | | `price_book_id` | string (uuid), nullable | no | ID of the price list the price comes from. | | `is_active` | boolean | yes | Whether it's active. | | `features` | array of string | no | Product features. | - `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 `catalog:read`. Example: ```json {"detail": "API key is missing required scope: catalog:read"} ``` - `404` — Product 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"} ``` --- # Orders — PapaProxy.net API reference Page: https://papaproxy.net/developers/reference/orders.php Last updated: November 9, 2026 Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/ ### Place an order `POST /api/v1/billing/composite/order` - Access: API key required · scope `services:write` Buy a proxy package (datacenter or ISP proxies) built from one or more segments (`[{country, count}]`): at least 100 IPs in total and at least 10 per country. Availability and price are re-checked at order time, and a single service is created with all segments. For dedicated proxies, use `POST /api/v1/billing/dedicated/order`. - `whitelist_ips` must contain at least one IPv4 address. Without it the response is `422` with `detail.error = "WHITELIST_REQUIRED"`. - With `use_balance=true`, your wallet balance is applied first. If it covers the total, the order is paid right away. Otherwise the service is created with `status: "pending"` and `payment_required: true`: `balance_applied_cents` is the part covered by your balance, and `amount_due_cents` is the remainder. Pay it in the Dashboard under Pending payments; the IPs are issued after payment. - With `use_balance=false` (the default), the whole amount is due the same way. - Pass `idempotency_key`: a retry with the same key returns the original order and doesn't charge again. Without a key, every request creates a new order, even with identical segments. Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `product_id` | string (uuid), nullable | no | Optional catalog product to base the order on. When omitted, the product is selected by `product_kind`. | | `segments` | array of object | yes | One or more segments forming the mix · Min items: 1 | | `segments[].country` | integer or string | yes | ISO 3166-1 alpha-2 country code ("DE", "GB"). A numeric country ID from `GET /api/v1/billing/countries` is also accepted. | | `segments[].count` | integer | yes | Number of IPs in this country. At least 10; the order total must be at least 100. · Minimum: 1 · Maximum: 100000 | | `product_kind` | string | no | Product: `proxy_datacenter` or `proxy_isp`. Dedicated proxies are priced and ordered through `/api/v1/billing/dedicated/*`. · Default: `"proxy_datacenter"` | | `period_months` | integer | no | Term length in months (1, 3, 6, or 12). Longer terms get a discount. Default: 1. | | `whitelist_ips` | array of string | no | IPv4 addresses to whitelist at order time. Proxy orders need at least one; without it the request fails with HTTP 422 and nothing is charged. | | `auto_renew` | boolean | no | Renew the service automatically when the term ends. Default: true. | | `use_balance` | boolean | no | Apply your wallet balance first. Default: `false` — the full amount is due through a payment processor and the service stays `pending` until it's paid. | | `geo_source` | string | no | Geo source for country lookups: maxmind (geolocation) or ripe (RIR allocation). Default maxmind. · One of: `maxmind`, `ripe` | | `idempotency_key` | string, nullable | no | Idempotency key. A retry with the same key returns the original order without charging again. Without a key, every request creates a new order. | | `promo_code` | string, nullable | no | Optional promo code. The discount applies to the first term only; renewals are charged at the regular price. · Max length: 64 | | `trial` | boolean | no | Order the paid 48-hour trial instead of a full term. · Default: `false` | | `extra_whitelist_count` | integer | no | Paid whitelist IPs beyond the free allowance. Included in the charge. · Minimum: 0 · Maximum: 10000 · Default: `0` | | `udp_unlock` | boolean | no | Add the UDP add-on. Included in the charge. · Default: `false` | | `extra_connections_count` | integer | no | Paid concurrent connections beyond those included. Included in the charge. For an existing service, buy the `extra_connections` add-on instead. · Minimum: 0 · Maximum: 10000 · Default: `0` | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/billing/composite/order" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"segments": [{"country": "US", "count": 100}], "period_months": 1, "whitelist_ips": ["203.0.113.10"], "use_balance": true, "idempotency_key": "order-2026-001"}' ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/billing/composite/order", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "segments": [ { "country": "US", "count": 100 } ], "period_months": 1, "whitelist_ips": [ "203.0.113.10" ], "use_balance": True, "idempotency_key": "order-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/composite/order", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "segments": [ { "country": "US", "count": 100 } ], "period_months": 1, "whitelist_ips": [ "203.0.113.10" ], "use_balance": true, "idempotency_key": "order-2026-001" }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'segments' => [[ 'country' => 'US', 'count' => 100 ]], 'period_months' => 1, 'whitelist_ips' => ['203.0.113.10'], 'use_balance' => true, 'idempotency_key' => 'order-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/composite/order", strings.NewReader(`{"segments": [{"country": "US", "count": 100}], "period_months": 1, "whitelist_ips": ["203.0.113.10"], "use_balance": true, "idempotency_key": "order-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` — The order was created. Check `payment_required` and `amount_due_cents`. Example response: ```json { "service_id": "dea53a24-1760-501d-a60a-d438bd176599", "status": "pending", "segments": [ { "country": 197148, "country_code": "US", "count": 100, "unit_base_cents": 19, "type_multiplier": 1.0, "geo_multiplier": 1.0, "segment_price_cents": 1900, "available": true } ], "total_ip": 100, "total_price_cents": 1900, "period_months": 1, "discount_pct": 0, "monthly_price_cents": 1900, "balance_applied_cents": 1777, "amount_due_cents": 123, "invoice_url": null, "payment_required": true, "intent_id": "2dbb7b45-5fb8-5bc9-834f-32aeaa569d72", "promo_code_applied": null, "promo_discount_cents": 0, "total_before_promo_cents": null, "trial": false, "trial_period_days": null, "addon_monthly_cents": 0 } ``` | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | yes | Created service ID | | `status` | string | yes | `active` when the order is paid, `pending` while `amount_due_cents` is unpaid. | | `segments` | array of object | no | Per-segment breakdown as ordered | | `segments[].country` | integer | yes | Numeric country ID. The ISO code is in `country_code`. | | `segments[].country_code` | string, nullable | no | ISO 3166-1 alpha-2 country code (e.g. "DE"). | | `segments[].count` | integer | yes | | | `segments[].unit_base_cents` | integer | yes | Volume-tier base unit price in integer cents per IP | | `segments[].type_multiplier` | number | yes | Product multiplier applied to the base rate. | | `segments[].geo_multiplier` | number | yes | Country multiplier (default 1.0). | | `segments[].segment_price_cents` | integer | yes | Segment price: count × base rate × product multiplier × country multiplier, rounded up to whole cents. | | `segments[].available` | boolean | yes | Whether enough IPs are available for this segment. | | `total_ip` | integer | yes | Total number of IPs. | | `total_price_cents` | integer | yes | Discounted total charged for the full period in integer cents | | `period_months` | integer | no | Billing period length in months (1/3/6/12) · Default: `1` | | `discount_pct` | integer | no | Applied period discount as a whole percentage (0/10/15/20) · Default: `0` | | `monthly_price_cents` | integer | no | Undiscounted monthly total (sum of segment prices) in integer cents · Default: `0` | | `balance_applied_cents` | integer | no | Amount paid from your wallet balance, in cents. Zero when `use_balance` is false. · Default: `0` | | `amount_due_cents` | integer | no | Amount still due through a payment processor, in cents. Zero when the balance covers the order. · Default: `0` | | `invoice_url` | string, nullable | no | Link to the invoice, if one was issued. | | `payment_required` | boolean | no | `true` when `amount_due_cents` must still be paid. Pay it in the Dashboard under Pending payments. · Default: `false` | | `intent_id` | string (uuid), nullable | no | ID of the pending payment when `payment_required` is `true`. | | `promo_code_applied` | string, nullable | no | Promo code applied to the order, if any. | | `promo_discount_cents` | integer | no | Promo discount applied to the order, in cents. · Minimum: 0 · Default: `0` | | `total_before_promo_cents` | integer, nullable | no | Pre-promo total when a promo was applied; null otherwise. | | `trial` | boolean | no | True when the service is a paid trial. · Default: `false` | | `trial_period_days` | integer, nullable | no | Trial length in days; null for regular orders. | | `addon_monthly_cents` | integer | no | Monthly cost of add-ons bought with the order, in cents. · Minimum: 0 · 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"} ``` - `422` — Validation error, fewer than 100 IPs in total, or no whitelisted IP. Example: ```json {"detail": {"error": "WHITELIST_REQUIRED", "message": "A proxy service requires at least one whitelisted IP at order time. …"}} ``` - `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"} ``` ### Quote additional IPs `POST /api/v1/billing/services/{service_id}/add-ips/quote` - Access: API key required Preview adding IPs to an active service: the prorated charge for the rest of the current term and the new renewal price. No charge and no changes to the service. Works with any valid API key. - Proxy packages: at least 100 IPs per request and at least 10 per country, as with new orders. A country below 10 IPs is listed in `errors`. - Dedicated proxies: from 1 IP. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `segments` | array of object | yes | One or more segments to add · Min items: 1 | | `segments[].country` | integer or string | yes | ISO 3166-1 alpha-2 country code ("DE", "GB"). A numeric country ID from `GET /api/v1/billing/countries` is also accepted. | | `segments[].count` | integer | yes | Number of IPs to add in this country. Proxy packages: at least 10 per country and 100 per request. Dedicated proxies: from 1. · Minimum: 1 · Maximum: 10000 | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/add-ips/quote" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"segments": [{"country": "US", "count": 100}]}' ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/add-ips/quote", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "segments": [ { "country": "US", "count": 100 } ] }, 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/add-ips/quote", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "segments": [ { "country": "US", "count": 100 } ] }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'segments' => [[ 'country' => 'US', 'count' => 100 ]] ]), ]); $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/add-ips/quote", strings.NewReader(`{"segments": [{"country": "US", "count": 100}]}`)) 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` — Non-binding preview of an add-ips top-up. Example response: ```json { "service_id": "cb532178-5b03-506a-84ea-0230ff28576b", "segments": [ { "country": 197148, "country_code": "US", "count": 100, "unit_base_cents": 19, "type_multiplier": 1.0, "geo_multiplier": 1.0, "segment_price_cents": 1900, "available": true } ], "errors": [], "added_ip_count": 100, "monthly_price_added_cents": 1900, "days_remaining": 60, "days_per_month": 30, "prorated_charge_cents": 3798, "current_price_cents": 9500, "new_renewal_price_cents": 11400 } ``` | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | yes | ID of the service. | | `segments` | array of object | no | Per-segment pricing breakdown for the added IPs | | `segments[].country` | integer | yes | Numeric country ID. The ISO code is in `country_code`. | | `segments[].country_code` | string, nullable | no | ISO 3166-1 alpha-2 country code (e.g. "DE"). | | `segments[].count` | integer | yes | | | `segments[].unit_base_cents` | integer | yes | Volume-tier base unit price in integer cents per IP | | `segments[].type_multiplier` | number | yes | Product multiplier applied to the base rate. | | `segments[].geo_multiplier` | number | yes | Country multiplier (default 1.0). | | `segments[].segment_price_cents` | integer | yes | Segment price: count × base rate × product multiplier × country multiplier, rounded up to whole cents. | | `segments[].available` | boolean | yes | Whether enough IPs are available for this segment. | | `errors` | array of object | no | Segments that can't be fulfilled; ordering them would fail with HTTP 409. | | `errors[].country` | integer | yes | Numeric country ID of the segment that was left out. | | `errors[].message` | string | yes | | | `added_ip_count` | integer | yes | Total IPs across the added segments | | `monthly_price_added_cents` | integer | yes | Undiscounted full-monthly cost of the added segments | | `days_remaining` | integer | yes | Days left in the current term. | | `days_per_month` | integer | no | Days per month used for proration. · Default: `30` | | `prorated_charge_cents` | integer | yes | Amount charged now for the remainder of the current period | | `current_price_cents` | integer | yes | Service's current full-period renewal price | | `new_renewal_price_cents` | integer | yes | Renewal price after this top-up (current + added segments' full-period cost) | - `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, or fewer than 100 IPs in a proxy-package request. 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"} ``` ### Add IPs to a service `POST /api/v1/billing/services/{service_id}/add-ips` - Access: API key required · scope `services:write` Add IPs to an active service — in the same country or new ones. You're charged the prorated amount for the rest of the current term, and the renewal price increases accordingly. Get the exact amount from `POST …/add-ips/quote` first. - Proxy packages: at least 100 IPs per request and at least 10 per country. Dedicated proxies: from 1 IP. - `idempotency_key` is required. A retry with the same key returns the original result and doesn't charge again. - Insufficient balance returns `402` with `detail.detail = "insufficient_funds"`, `required_cents`, `balance_cents`, and `deficit_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 | |---|---|---|---| | `segments` | array of object | yes | One or more segments to add · Min items: 1 | | `segments[].country` | integer or string | yes | ISO 3166-1 alpha-2 country code ("DE", "GB"). A numeric country ID from `GET /api/v1/billing/countries` is also accepted. | | `segments[].count` | integer | yes | Number of IPs to add in this country. Proxy packages: at least 10 per country and 100 per request. Dedicated proxies: from 1. · Minimum: 1 · Maximum: 10000 | | `idempotency_key` | string | yes | Client-supplied idempotency key. Must be unique per intent. · Min length: 1 | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/billing/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/add-ips" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"segments": [{"country": "US", "count": 100}], "idempotency_key": "add-ips-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/add-ips", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "segments": [ { "country": "US", "count": 100 } ], "idempotency_key": "add-ips-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/add-ips", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "segments": [ { "country": "US", "count": 100 } ], "idempotency_key": "add-ips-2026-001" }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'segments' => [[ 'country' => 'US', 'count' => 100 ]], 'idempotency_key' => 'add-ips-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/add-ips", strings.NewReader(`{"segments": [{"country": "US", "count": 100}], "idempotency_key": "add-ips-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` — Result of a successful add-ips top-up. Example response: ```json { "service_id": "cb532178-5b03-506a-84ea-0230ff28576b", "added_ip_count": 100, "charged_cents": 3799, "ledger_entry_id": "18e0387a-1131-5ccd-a3f7-2d406d4cf5b3", "new_total_ip": 500, "new_price_cents": 9500, "segments": [ { "count": 500, "country": 197148 } ] } ``` | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | yes | ID of the service. | | `added_ip_count` | integer | yes | Number of IPs added. | | `charged_cents` | integer | yes | Prorated amount charged to the wallet | | `ledger_entry_id` | string (uuid), nullable | no | Wallet transaction for the charge; null when nothing was charged. | | `new_total_ip` | integer | yes | Total IP count on the service after the top-up | | `new_price_cents` | integer | yes | Service's new full-period renewal price | | `segments` | array of object | no | All segments of the service after the top-up. | - `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": {"detail": "insufficient_funds", "required_cents": 3799, "balance_cents": 1777, "deficit_cents": 2022}} ``` - `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 (for example, a missing `idempotency_key`), or fewer than 100 IPs in a proxy-package request. 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"} ``` --- # Dedicated proxies — PapaProxy.net API reference Page: https://papaproxy.net/developers/reference/dedicated-proxies.php Last updated: November 9, 2026 Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/ ### List dedicated-proxy countries `GET /api/v1/billing/dedicated/countries` - Access: No API key required Countries with dedicated IPs in stock and the number of available IPs. Only countries with at least one IP are listed. No API key required. Use the same `geo_source` and `product_kind` in quote and order requests. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `geo_source (query)` | string | no | Geo source for country lookups: `maxmind` (geolocation) or `ripe` (RIR allocation). Use the same value in countries, quote, and order requests. · One of: `maxmind`, `ripe` · Default: `"maxmind"` | | `product_kind (query)` | string | no | Dedicated datacenter (`proxy_datacenter`) or dedicated ISP (`proxy_isp`) proxies. · One of: `proxy_datacenter`, `proxy_isp` · Default: `"proxy_datacenter"` | Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/billing/dedicated/countries" ``` Python: ```python import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/billing/dedicated/countries", timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/dedicated/countries", { method: "GET", }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'GET', CURLOPT_RETURNTRANSFER => true, ]); $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" ) func main() { req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/billing/dedicated/countries", nil) if err != nil { panic(err) } 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` — Countries with dedicated IPs in stock. Example response: ```json { "countries": [ { "country": "IL", "available_ips": 453 } ], "geo_source": "maxmind", "product_kind": "proxy_datacenter", "offered_kinds": [ "proxy_datacenter", "proxy_isp" ] } ``` | Field | Type | Required | Description | |---|---|---|---| | `countries` | array of object | no | Countries with at least one IP available. | | `countries[].country` | string | yes | ISO 3166-1 alpha-2 country code. | | `countries[].available_ips` | integer | yes | Number of dedicated IPs currently available. | | `geo_source` | string | no | Geo source the counts are for. · One of: `maxmind`, `ripe` · Default: `"maxmind"` | | `product_kind` | string | no | Product the counts are for. · One of: `proxy_datacenter`, `proxy_isp` · Default: `"proxy_datacenter"` | | `offered_kinds` | array of string | no | Dedicated products you can order. | - `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 dedicated-proxy quote `POST /api/v1/billing/dedicated/quote` - Access: API key optional Price dedicated proxies: from 1 IP per order, with no per-country minimum. The quote doesn't reserve IPs. - Countries are ISO codes only. A country that's out of stock still gets a price: check that `segments[].available` is at least `count` before ordering, or the order fails with `409`. - The per-IP rate (`unit_millicents`) depends on the total IP count; term discounts apply on top. - Pass `total_price_cents` to the order as `expected_total_cents`. The API key is optional. Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `segments` | array of object | yes | One or more countries with IP counts. · Min items: 1 | | `segments[].country` | string | yes | ISO 3166-1 alpha-2 country code ("IL", "US"). Numeric IDs aren't accepted. | | `segments[].count` | integer | yes | Number of dedicated IPs in this country. From 1. · Minimum: 1 | | `product_kind` | string | no | Dedicated datacenter (`proxy_datacenter`) or dedicated ISP (`proxy_isp`) proxies. · One of: `proxy_datacenter`, `proxy_isp` · Default: `"proxy_datacenter"` | | `period_months` | integer | no | Term in months. Longer terms get a discount: 10% for 3 months, 15% for 6, 20% for 12. · One of: `1`, `3`, `6`, `12` · Default: `1` | | `geo_source` | string | no | Geo source for country lookups: `maxmind` (geolocation) or `ripe` (RIR allocation). Use the same value in countries, quote, and order requests. · One of: `maxmind`, `ripe` · Default: `"maxmind"` | | `promo_code` | string, nullable | no | Optional promo code. | | `udp_unlock` | boolean | no | Add UDP (50% of the base price). · Default: `false` | | `extra_connections_count` | integer | no | Paid concurrent connections beyond those included. · Default: `0` | | `extra_whitelist_count` | integer | no | Paid whitelisted IPs beyond the free allowance. · Default: `0` | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/billing/dedicated/quote" \ -H "Content-Type: application/json" \ -d '{"segments": [{"country": "IL", "count": 2}], "period_months": 3}' ``` Python: ```python import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/billing/dedicated/quote", json={ "segments": [ { "country": "IL", "count": 2 } ], "period_months": 3 }, timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/dedicated/quote", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ "segments": [ { "country": "IL", "count": 2 } ], "period_months": 3 }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'segments' => [[ 'country' => 'IL', 'count' => 2 ]], 'period_months' => 3 ]), ]); $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" "strings" ) func main() { req, err := http.NewRequest("POST", "https://papaproxy.net/account_new/api/v1/billing/dedicated/quote", strings.NewReader(`{"segments": [{"country": "IL", "count": 2}], "period_months": 3}`)) if err != nil { panic(err) } 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` — Price quote. Check `segments[].available` before ordering. Example response: ```json { "unit_millicents": 220000, "total_price_cents": 1188, "ip_count": 2, "min_ips_per_order": 1, "segments": [ { "country": "IL", "count": 2, "available": 453 } ], "geo_source": "maxmind", "product_kind": "proxy_datacenter", "dedicated_class": "dc", "period_months": 3, "monthly_price_cents": 440, "segment_monthly_cents": 440, "addon_monthly_cents": 0, "discount_pct": 10, "partner_discount_pct": null, "promo_code": null, "promo_discount_cents": 0, "total_before_promo_cents": 1188 } ``` | Field | Type | Required | Description | |---|---|---|---| | `unit_millicents` | integer | no | Monthly price per IP, in millicents (thousandths of a cent). Depends on the total IP count. | | `total_price_cents` | integer | no | Total for the whole term, in cents. Pass it as `expected_total_cents` when ordering. | | `ip_count` | integer | no | Total number of IPs. | | `min_ips_per_order` | integer | no | Minimum IPs per order (1 for dedicated proxies). | | `segments` | array of object | no | Requested countries with current stock. | | `segments[].country` | string | no | ISO 3166-1 alpha-2 country code. | | `segments[].count` | integer | no | Requested number of IPs. | | `segments[].available` | integer | no | IPs currently in stock. If it's lower than `count`, an order fails with `409` `dedicated_out_of_stock`. | | `geo_source` | string | no | Geo source used. · One of: `maxmind`, `ripe` · Default: `"maxmind"` | | `product_kind` | string | no | Product priced. · One of: `proxy_datacenter`, `proxy_isp` · Default: `"proxy_datacenter"` | | `dedicated_class` | string | no | `dc` for datacenter, `isp` for ISP. | | `period_months` | integer | no | Term in months. | | `monthly_price_cents` | integer | no | Monthly price, including add-ons, before the term discount. | | `segment_monthly_cents` | integer | no | Monthly price of the IPs alone. | | `addon_monthly_cents` | integer | no | Monthly price of add-ons. | | `discount_pct` | integer | no | Term discount, in percent. | | `partner_discount_pct` | integer, nullable | no | Partner discount, in percent, if your account has one. | | `promo_code` | string, nullable | no | Applied promo code. | | `promo_discount_cents` | integer | no | Promo discount, in cents. | | `total_before_promo_cents` | integer | no | Total before the promo discount, in cents. | - `422` — Validation error: an empty `segments` list, a `count` below 1, a country that isn't an ISO code, or an unsupported `period_months`. 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"} ``` - `500` — Currently returned for an unrecognized `promo_code` instead of a validation error. Check the promo code and retry without it. ### Order dedicated proxies `POST /api/v1/billing/dedicated/order` - Access: API key required · scope `services:write` Buy dedicated proxies — IPs reserved exclusively for you — from 1 IP. Get the price from `POST /api/v1/billing/dedicated/quote` first. - Send the `Idempotency-Key` header (1–128 characters). A retry with the same key and body returns the original order and doesn't charge again (`balance_applied_cents` is `0` in the repeated response). The same key with a different body returns `400`. The key goes in the header, not in the body. - `expected_total_cents` is required: pass `total_price_cents` from the quote. If the price has changed, the response is `409` with `detail.code = "price_changed"` and the current `actual_total_cents`. - If a country doesn't have enough IPs, the response is `409` with `detail.code = "dedicated_out_of_stock"` and the shortfall per country. - `whitelist_ips` must contain at least one IPv4 address. - With `use_balance=true`, your wallet balance is applied first; if it covers the total, the service is `active` right away. Otherwise, and with `use_balance=false` (the default), the service is `pending` until `amount_due_cents` is paid in the Dashboard under Pending payments. - Fields that other order endpoints accept — `idempotency_key`, `trial`, `addons`, `expected_price_cents` — are rejected here with `422`. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `Idempotency-Key (header)` | string | yes | Unique key for this order. Reuse it only to retry the same request. · Min length: 1 · Max length: 128 | Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `segments` | array of object | yes | One or more countries with IP counts. · Min items: 1 | | `segments[].country` | string | yes | ISO 3166-1 alpha-2 country code ("IL", "US"). Numeric IDs aren't accepted. | | `segments[].count` | integer | yes | Number of dedicated IPs in this country. From 1. · Minimum: 1 | | `expected_total_cents` | integer | yes | `total_price_cents` from the quote you showed the user. Without it the response is `422`; if the price has changed, `409` with the current `actual_total_cents`. | | `whitelist_ips` | array of string | yes | IPv4 addresses to whitelist. At least one is required. · Min items: 1 | | `product_kind` | string | no | Dedicated datacenter (`proxy_datacenter`) or dedicated ISP (`proxy_isp`) proxies. · One of: `proxy_datacenter`, `proxy_isp` · Default: `"proxy_datacenter"` | | `period_months` | integer | no | Term in months. Longer terms get a discount: 10% for 3 months, 15% for 6, 20% for 12. · One of: `1`, `3`, `6`, `12` · Default: `1` | | `geo_source` | string | no | Geo source for country lookups: `maxmind` (geolocation) or `ripe` (RIR allocation). Use the same value in countries, quote, and order requests. · One of: `maxmind`, `ripe` · Default: `"maxmind"` | | `use_balance` | boolean | no | Apply your wallet balance first. Default: `false` — the service is created as `pending` and the full amount is due in the Dashboard under Pending payments. | | `auto_renew` | boolean | no | Renew automatically when the term ends. | | `promo_code` | string, nullable | no | Optional promo code. | | `udp_unlock` | boolean | no | Add UDP (50% of the base price). · Default: `false` | | `extra_connections_count` | integer | no | Paid concurrent connections beyond those included. · Default: `0` | | `extra_whitelist_count` | integer | no | Paid whitelisted IPs beyond the free allowance. · Default: `0` | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/billing/dedicated/order" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Idempotency-Key: dedicated-order-2026-001" \ -H "Content-Type: application/json" \ -d '{"segments": [{"country": "IL", "count": 1}], "expected_total_cents": 220, "whitelist_ips": ["203.0.113.10"], "use_balance": true}' ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/billing/dedicated/order", headers={ "Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}", "Idempotency-Key": "dedicated-order-2026-001", }, json={ "segments": [ { "country": "IL", "count": 1 } ], "expected_total_cents": 220, "whitelist_ips": [ "203.0.113.10" ], "use_balance": True }, timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/billing/dedicated/order", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Idempotency-Key": "dedicated-order-2026-001", "Content-Type": "application/json", }, body: JSON.stringify({ "segments": [ { "country": "IL", "count": 1 } ], "expected_total_cents": 220, "whitelist_ips": [ "203.0.113.10" ], "use_balance": true }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Idempotency-Key: dedicated-order-2026-001', 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'segments' => [[ 'country' => 'IL', 'count' => 1 ]], 'expected_total_cents' => 220, 'whitelist_ips' => ['203.0.113.10'], 'use_balance' => true ]), ]); $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/dedicated/order", strings.NewReader(`{"segments": [{"country": "IL", "count": 1}], "expected_total_cents": 220, "whitelist_ips": ["203.0.113.10"], "use_balance": true}`)) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY")) req.Header.Set("Idempotency-Key", "dedicated-order-2026-001") 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` — The order was created. Check `payment_required` and `amount_due_cents`. Example response: ```json { "service_id": "1300d2ec-f511-573c-9869-93c88c501084", "status": "active", "ip_count": 1, "total_price_cents": 220, "period_months": 1, "monthly_price_cents": 220, "balance_applied_cents": 220, "amount_due_cents": 0, "payment_required": false, "intent_id": null } ``` | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | no | ID of the new service. | | `status` | string | no | `active` when the order is paid, `pending` while `amount_due_cents` is unpaid. | | `ip_count` | integer | no | Number of IPs ordered. | | `total_price_cents` | integer | no | Total for the term, in cents. | | `period_months` | integer | no | Term in months. | | `monthly_price_cents` | integer | no | Monthly price, in cents. | | `balance_applied_cents` | integer | no | Part of the total covered by your wallet balance. A retry with the same `Idempotency-Key` returns `0` here, because nothing is charged again. | | `amount_due_cents` | integer | no | Amount still to be paid, in cents. | | `payment_required` | boolean | no | `true` when `amount_due_cents` must still be paid in the Dashboard. | | `intent_id` | string (uuid), nullable | no | ID of the pending payment, if any. | - `400` — The `Idempotency-Key` was already used with a different request body. Example: ```json {"detail": "Idempotency-Key reused with a different dedicated order body"} ``` - `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"} ``` - `409` — The price changed, or a country doesn't have enough IPs in stock. Nothing is charged. Example (price changed): ```json {"detail": {"code": "price_changed", "actual_total_cents": 220}} ``` Example (out of stock): ```json {"detail": {"code": "dedicated_out_of_stock", "per_country": {"US": {"requested": 1, "available": 0}}}} ``` - `422` — Validation error, a missing `Idempotency-Key` header or `expected_total_cents`, or no whitelisted IP. Example (idempotency key required): ```json {"detail": {"code": "idempotency_key_required", "message": "Idempotency-Key header is required (1..128 chars)."}} ``` Example (price confirmation required): ```json {"detail": {"code": "price_confirmation_required", "message": "expected_total_cents is required (price confirmation)."}} ``` Example (whitelist required): ```json {"detail": {"error": "WHITELIST_REQUIRED", "message": "A proxy service requires at least one whitelisted IP at order time. …"}} ``` - `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"} ``` --- # 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 '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 '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 '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 '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 '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 '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"} ``` --- # Proxy access — PapaProxy.net API reference Page: https://papaproxy.net/developers/reference/proxy-access.php Last updated: November 9, 2026 Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/ ### List proxy IPs `GET /api/v1/customer/services/{service_id}/proxy/ips` - Access: API key required · scope `services:read` List the proxy IP endpoints 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/proxy/ips" \ -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/proxy/ips", 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/proxy/ips", { 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 '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/proxy/ips", 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` — List of proxy IPs for a service. Example response: ```json { "service_id": "cb532178-5b03-506a-84ea-0230ff28576b", "ips": [ { "ip": "192.0.2.10", "country": "US", "type": "socks5" }, { "ip": "192.0.2.11", "country": "US", "type": "socks5" } ], "format_hints": {}, "geo_source": "maxmind" } ``` | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | yes | ID of the service. | | `ips` | array of object | yes | | | `ips[].ip` | string | yes | IPv4 or IPv6 address | | `ips[].port` | integer, nullable | no | Minimum: 1 · Maximum: 65535 | | `ips[].country` | string, nullable | no | ISO 3166-1 alpha-2 country code of the IP. | | `ips[].type` | string, nullable | no | | | `format_hints` | object, nullable | no | Display hints for proxy IP list rendering. | | `format_hints.host_port` | string, nullable | no | | | `format_hints.auth_url` | string, nullable | no | | | `geo_source` | string, 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: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"} ``` ### Get proxy credentials `GET /api/v1/customer/services/{service_id}/proxy/auth` - Access: API key required · scope `services:read` Get the proxy connection credentials for one of your services. The password is masked by default; pass `reveal=true` to receive it in plain text. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | | `reveal (query)` | boolean | no | Default: `false` | Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/auth" \ -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/proxy/auth", 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/proxy/auth", { 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 '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/proxy/auth", 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` — Current proxy auth status. Example response: ```json { "username": "pp_user_3fa85f64", "protocols": [ "socks5", "http", "https" ], "endpoints": [ { "protocol": "socks5", "label": "SOCKS5", "scheme": "socks5", "port": 1080, "port_whitelist": 1085, "http2": false, "udp": true, "noauth_only": false }, { "protocol": "http", "label": "HTTP", "scheme": "http", "port": 8080, "port_whitelist": 8085, "http2": false, "udp": false, "noauth_only": false } ], "whitelist_ips": [ "203.0.113.11" ], "password_set": true, "udp_enabled": false } ``` | Field | Type | Required | Description | |---|---|---|---| | `username` | string | yes | Proxy username. | | `password` | string, nullable | no | | | `protocols` | array of string | no | | | `endpoints` | array of object | no | | | `endpoints[].protocol` | string | yes | | | `endpoints[].label` | string | yes | | | `endpoints[].scheme` | string | yes | | | `endpoints[].port` | integer, nullable | no | | | `endpoints[].port_whitelist` | integer, nullable | no | | | `endpoints[].http2` | boolean | no | Default: `false` | | `endpoints[].udp` | boolean | no | Default: `false` | | `endpoints[].noauth_only` | boolean | no | Default: `false` | | `endpoints[].hostname_template` | string, nullable | no | | | `whitelist_ips` | array of string | no | Whitelisted IPv4 addresses. | | `password_set` | boolean | no | Default: `false` | | `udp_enabled` | boolean | no | Whether UDP is enabled. · Default: `false` | | `conn_cap` | integer, nullable | no | Concurrent connection limit of the service. | | `whitelist_conn_limits` | object, nullable | no | | | `rotating_gateway` | object, nullable | no | Rotating gateway hostnames and ports for this service. The list fields are authoritative; `entry_host` and `https_host` are the first entries. Lists are empty when a gateway type isn't available for the service. | | `rotating_gateway.entry_hosts` | array of string | no | | | `rotating_gateway.https_hosts` | array of string | no | | | `rotating_gateway.sticky_hosts` | array of string | no | | | `rotating_gateway.entry_host` | string, nullable | no | | | `rotating_gateway.https_host` | string, nullable | no | | | `rotating_gateway.http_ports` | array of integer | no | HTTP ports. | | `rotating_gateway.socks_ports` | array of integer | no | SOCKS5 ports. | | `rotating_gateway.sticky_http` | integer | no | Default: `5080` | | `rotating_gateway.sticky_socks5` | integer | no | Default: `4080` | | `rotating_gateway.sticky_https` | integer | no | Default: `4443` | - `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"} ``` ### Change proxy credentials `PUT /api/v1/customer/services/{service_id}/proxy/auth` - Access: API key required · scope `services:write` Update proxy credentials — rotate the password or change the login. Idempotent: a repeated identical payload produces the same state. When rotating, the new password is returned exactly once in the response body. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `rotate_password` | boolean | no | Default: `false` | | `udp_enabled` | boolean, nullable | no | Whether UDP is enabled. | | `new_username` | string, nullable | no | New proxy login. Must not be taken by another account. · Min length: 3 · Max length: 32 | | `whitelist_ips` | array of string, nullable | no | Whitelisted IPv4 addresses. | Request examples: cURL: ```bash curl -X PUT "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/auth" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"whitelist_ips": ["203.0.113.10"]}' ``` Python: ```python import os import requests resp = requests.put( "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/auth", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "whitelist_ips": [ "203.0.113.10" ] }, 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/proxy/auth", { method: "PUT", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "whitelist_ips": [ "203.0.113.10" ] }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'PUT', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'whitelist_ips' => ['203.0.113.10'] ]), ]); $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("PUT", "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/auth", strings.NewReader(`{"whitelist_ips": ["203.0.113.10"]}`)) 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` — Current proxy auth status. Example response: ```json { "username": "pp_user_3fa85f64", "protocols": [ "socks5", "http", "https" ], "endpoints": [ { "protocol": "socks5", "label": "SOCKS5", "scheme": "socks5", "port": 1080, "port_whitelist": 1085, "http2": false, "udp": true, "noauth_only": false }, { "protocol": "http", "label": "HTTP", "scheme": "http", "port": 8080, "port_whitelist": 8085, "http2": false, "udp": false, "noauth_only": false } ], "whitelist_ips": [ "203.0.113.11" ], "password_set": true, "udp_enabled": false } ``` | Field | Type | Required | Description | |---|---|---|---| | `username` | string | yes | Proxy username. | | `password` | string, nullable | no | | | `protocols` | array of string | no | | | `endpoints` | array of object | no | | | `endpoints[].protocol` | string | yes | | | `endpoints[].label` | string | yes | | | `endpoints[].scheme` | string | yes | | | `endpoints[].port` | integer, nullable | no | | | `endpoints[].port_whitelist` | integer, nullable | no | | | `endpoints[].http2` | boolean | no | Default: `false` | | `endpoints[].udp` | boolean | no | Default: `false` | | `endpoints[].noauth_only` | boolean | no | Default: `false` | | `endpoints[].hostname_template` | string, nullable | no | | | `whitelist_ips` | array of string | no | Whitelisted IPv4 addresses. | | `password_set` | boolean | no | Default: `false` | | `udp_enabled` | boolean | no | Whether UDP is enabled. · Default: `false` | | `conn_cap` | integer, nullable | no | Concurrent connection limit of the service. | | `whitelist_conn_limits` | object, nullable | no | | | `rotating_gateway` | object, nullable | no | Rotating gateway hostnames and ports for this service. The list fields are authoritative; `entry_host` and `https_host` are the first entries. Lists are empty when a gateway type isn't available for the service. | | `rotating_gateway.entry_hosts` | array of string | no | | | `rotating_gateway.https_hosts` | array of string | no | | | `rotating_gateway.sticky_hosts` | array of string | no | | | `rotating_gateway.entry_host` | string, nullable | no | | | `rotating_gateway.https_host` | string, nullable | no | | | `rotating_gateway.http_ports` | array of integer | no | HTTP ports. | | `rotating_gateway.socks_ports` | array of integer | no | SOCKS5 ports. | | `rotating_gateway.sticky_http` | integer | no | Default: `5080` | | `rotating_gateway.sticky_socks5` | integer | no | Default: `4080` | | `rotating_gateway.sticky_https` | integer | no | Default: `4443` | - `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"} ``` ### List whitelisted IPs `GET /api/v1/customer/services/{service_id}/proxy/whitelist` - Access: API key required · scope `services:read` List the IP whitelist entries of one of your proxy 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/proxy/whitelist" \ -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/proxy/whitelist", 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/proxy/whitelist", { 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 '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/proxy/whitelist", 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` — Whitelisted IPs and the service's concurrent connection limit. Example response: ```json { "service_id": "cb532178-5b03-506a-84ea-0230ff28576b", "entries": [ { "ip_or_cidr": "203.0.113.11", "conn_limit": 1500, "is_paid": false } ], "conn_cap": 1500, "capacity": 5, "free_quota": 5, "used": 1, "paid_slots": 0 } ``` | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | yes | ID of the service. | | `entries` | array of object | yes | | | `entries[].ip_or_cidr` | string | yes | | | `entries[].label` | string, nullable | no | | | `entries[].conn_limit` | integer, nullable | no | Concurrent connections allowed from this IP. | | `entries[].is_paid` | boolean | no | Default: `false` | | `conn_cap` | integer | yes | Concurrent connection limit of the service. | | `capacity` | integer | no | Default: `0` | | `free_quota` | integer | no | Default: `0` | | `used` | integer | no | Default: `0` | | `paid_slots` | integer | no | 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: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"} ``` ### Add a whitelisted IP `POST /api/v1/customer/services/{service_id}/proxy/whitelist` - Access: API key required · scope `services:write` Add a single IPv4 address to the service's IP whitelist. Only single addresses are accepted (no CIDR subnets). Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `ip_or_cidr` | string | yes | Single IPv4 address (CIDR subnets are not supported) | | `label` | string, nullable | no | | | `conn_limit` | integer, nullable | no | Concurrent connections allowed from this IP. · Minimum: 0 | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/whitelist" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"ip_or_cidr": "203.0.113.10"}' ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/whitelist", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "ip_or_cidr": "203.0.113.10" }, 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/proxy/whitelist", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "ip_or_cidr": "203.0.113.10" }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'ip_or_cidr' => '203.0.113.10' ]), ]); $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/proxy/whitelist", strings.NewReader(`{"ip_or_cidr": "203.0.113.10"}`)) 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` — Whitelisted IPs and the service's concurrent connection limit. Example response: ```json { "service_id": "cb532178-5b03-506a-84ea-0230ff28576b", "entries": [ { "ip_or_cidr": "203.0.113.11", "conn_limit": 750, "is_paid": false }, { "ip_or_cidr": "203.0.113.77", "conn_limit": 750, "is_paid": false } ], "conn_cap": 1500, "capacity": 5, "free_quota": 5, "used": 2, "paid_slots": 0 } ``` | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | yes | ID of the service. | | `entries` | array of object | yes | | | `entries[].ip_or_cidr` | string | yes | | | `entries[].label` | string, nullable | no | | | `entries[].conn_limit` | integer, nullable | no | Concurrent connections allowed from this IP. | | `entries[].is_paid` | boolean | no | Default: `false` | | `conn_cap` | integer | yes | Concurrent connection limit of the service. | | `capacity` | integer | no | Default: `0` | | `free_quota` | integer | no | Default: `0` | | `used` | integer | no | Default: `0` | | `paid_slots` | integer | no | 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` — 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"} ``` ### Remove a whitelisted IP `DELETE /api/v1/customer/services/{service_id}/proxy/whitelist` - Access: API key required · scope `services:write` Remove an IP address from the service's IP whitelist. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | | `ip_or_cidr (query)` | string | yes | IP address to remove from the whitelist | Request examples: cURL: ```bash curl -X DELETE "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/whitelist?ip_or_cidr=203.0.113.10" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.delete( "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/whitelist?ip_or_cidr=203.0.113.10", 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/proxy/whitelist?ip_or_cidr=203.0.113.10", { 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 '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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/whitelist?ip_or_cidr=203.0.113.10", 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` — Whitelisted IPs and the service's concurrent connection limit. Example response: ```json { "service_id": "cb532178-5b03-506a-84ea-0230ff28576b", "entries": [ { "ip_or_cidr": "203.0.113.11", "conn_limit": 1500, "is_paid": false } ], "conn_cap": 1500, "capacity": 5, "free_quota": 5, "used": 1, "paid_slots": 0 } ``` | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | yes | ID of the service. | | `entries` | array of object | yes | | | `entries[].ip_or_cidr` | string | yes | | | `entries[].label` | string, nullable | no | | | `entries[].conn_limit` | integer, nullable | no | Concurrent connections allowed from this IP. | | `entries[].is_paid` | boolean | no | Default: `false` | | `conn_cap` | integer | yes | Concurrent connection limit of the service. | | `capacity` | integer | no | Default: `0` | | `free_quota` | integer | no | Default: `0` | | `used` | integer | no | Default: `0` | | `paid_slots` | integer | no | 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` — 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"} ``` ### Check IP refresh cost `GET /api/v1/customer/services/{service_id}/proxy/refresh-status` - Access: API key required · scope `services:read` Check whether the next IP refresh for this service is free or paid. Read-only. 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/proxy/refresh-status" \ -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/proxy/refresh-status", 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/proxy/refresh-status", { 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 '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/proxy/refresh-status", 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` — Status of the next IP reallocation for a service. Example response: ```json { "service_id": "cb532178-5b03-506a-84ea-0230ff28576b", "is_free": false, "free_window_days": 8, "days_since_start": 0, "next_free_refresh_at": "2026-11-15T14:26:50.787259Z", "price_cents": 1000, "is_pool_service": true } ``` | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | yes | ID of the service. | | `is_free` | boolean | yes | | | `free_window_days` | integer | yes | | | `days_since_start` | integer, nullable | yes | | | `next_free_refresh_at` | string (date-time), nullable | no | | | `price_cents` | integer | yes | Price in cents. | | `is_pool_service` | boolean | yes | Whether the service uses a shared IP pool. | - `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"} ``` ### Refresh proxy IPs `POST /api/v1/customer/services/{service_id}/proxy/refresh` - Access: API key required · scope `services:write` Replace the IPs of a proxy service. Pass `target_ips` to replace only specific addresses, or omit it to refresh the whole list. For dedicated proxies, `target_ips` is required; without it the response is `422` with `detail.code = "dedicated_refresh_target_required"`. Scheduled refreshes are free; an off-cycle refresh is charged to your wallet — check `GET …/proxy/refresh-status` first. Pass `idempotency_key` so a retried call can't charge twice. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `idempotency_key` | string | yes | Unique key for this refresh intent; retries with the same key return the original result and never charge twice. | | `target_ips` | array of string, nullable | no | Specific currently-allocated IPs to replace. Omit to refresh the entire allocation. | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/refresh" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"idempotency_key": "refresh-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/proxy/refresh", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "idempotency_key": "refresh-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/proxy/refresh", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "idempotency_key": "refresh-2026-001" }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'idempotency_key' => 'refresh-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/proxy/refresh", strings.NewReader(`{"idempotency_key": "refresh-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` — Result of a successful IP reallocation. | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | yes | ID of the service. | | `ip_count` | integer | yes | Number of IPs. | | `charged_cents` | integer | yes | Amount charged to your wallet, in cents. | | `ledger_entry_id` | string (uuid), nullable | yes | | | `new_ips` | array of string | 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."} ``` - `422` — Validation error, or `target_ips` is missing for a dedicated service. Example: ```json {"detail": {"code": "dedicated_refresh_target_required", "message": "…"}} ``` - `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"} ``` --- # Rotating sessions — PapaProxy.net API reference Page: https://papaproxy.net/developers/reference/rotating-sessions.php Last updated: November 9, 2026 Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/ ### Get rotating gateway settings `GET /api/v1/customer/services/{service_id}/proxy/config` - Access: API key required · scope `services:read` Gateway hostnames and ports (HTTP, HTTPS, SOCKS5) of a rotating proxy service. 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/proxy/config" \ -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/proxy/config", 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/proxy/config", { 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 '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/proxy/config", 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` — Connection settings of a rotating proxy service. Example response: ```json { "endpoint": "papaproxy.pro", "endpoints": [ "papaproxy.pro" ], "https_endpoints": [ "eu01.gateway.example.com", "us01.gateway.example.com" ], "port": 3081, "http_ports": [ 3081, 3082, 3083, 3084, 3085, 3086, 3087, 3088, 3089, 3090, 3091, 3092, 3093, 3094 ], "socks_ports": [ 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, 2092, 2093, 2094 ], "sticky_http": 5080, "sticky_socks5": 4080, "sticky_https": 4443, "session_format": "ip:port:username:password", "mode": "sticky", "sample_connection_string": "papaproxy.pro:3081:pp_user_3fa85f64:Xk9mQ2vL7pRt", "sample_connection_strings": [ "papaproxy.pro:3081:pp_user_3fa85f64:Xk9mQ2vL7pRt" ] } ``` | Field | Type | Required | Description | |---|---|---|---| | `endpoint` | string | yes | | | `endpoints` | array of string | no | | | `https_endpoints` | array of string | no | | | `port` | integer | yes | | | `http_ports` | array of integer | no | HTTP ports. | | `socks_ports` | array of integer | no | SOCKS5 ports. | | `sticky_http` | integer, nullable | no | | | `sticky_socks5` | integer, nullable | no | | | `sticky_https` | integer, nullable | no | | | `session_format` | string | no | Default: `"ip:port:username:password"` | | `mode` | string | no | Default: `"rotating"` | | `sample_connection_string` | string, nullable | no | | | `sample_connection_strings` | array of string | 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: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"} ``` ### List sticky sessions `GET /api/v1/customer/services/{service_id}/proxy/sticky/sessions` - Access: API key required · scope `services:read` List sticky sessions for a rotating proxy service. 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/proxy/sticky/sessions" \ -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/proxy/sticky/sessions", 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/proxy/sticky/sessions", { 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 '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/proxy/sticky/sessions", 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 sticky sessions for a rotating service. Example response: ```json { "service_id": "3bd2e419-2dc6-52bb-9d74-0a4560d55f53", "username": "pp_user_3fa85f64", "sessions": [] } ``` | Field | Type | Required | Description | |---|---|---|---| | `service_id` | string (uuid) | yes | ID of the service. | | `username` | string | yes | Proxy username. | | `sessions` | array of object | yes | | | `sessions[].sid` | string | yes | Sticky session ID. | | `sessions[].session_id` | string | yes | | | `sessions[].server` | string | yes | | | `sessions[].exit` | string | yes | Exit IP of the session. | | `sessions[].exit_addr` | string | yes | Exit IP and port of the session. | | `sessions[].is_default` | boolean | no | Default: `false` | | `sessions[].created` | string, nullable | no | | | `sessions[].last_used` | string, nullable | no | | | `sessions[].ttl_seconds` | integer, nullable | no | Sticky session duration, in seconds. | | `sessions[].expires_at` | string, nullable | no | When the current term ends (ISO 8601, UTC). | | `sessions[].mode` | string, 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: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"} ``` ### Rotate a sticky session `POST /api/v1/customer/services/{service_id}/proxy/sticky/sessions/{sid}/rotate` - Access: API key required · scope `services:write` Force a new exit IP for one sticky session. Free; this is not an IP list refresh. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | | `sid (path)` | string | yes | ID of the sticky session. | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/sessions/string/rotate" \ -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/proxy/sticky/sessions/string/rotate", 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/proxy/sticky/sessions/string/rotate", { 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 '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/proxy/sticky/sessions/string/rotate", 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 moving one sticky session to a new exit IP. | Field | Type | Required | Description | |---|---|---|---| | `sid` | string | yes | Sticky session ID. | | `exit` | string | yes | Exit IP of the session. | | `exit_addr` | string | yes | Exit IP and port of the session. | | `status` | string | no | Default: `"rotated"` | - `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` — Sticky session 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"} ``` ### End a sticky session `DELETE /api/v1/customer/services/{service_id}/proxy/sticky/sessions/{sid}` - Access: API key required · scope `services:write` End one sticky session; its next request gets a new exit IP. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | | `sid (path)` | string | yes | ID of the sticky session. | Request examples: cURL: ```bash curl -X DELETE "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/sessions/string" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.delete( "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/sessions/string", 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/proxy/sticky/sessions/string", { 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 '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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/sessions/string", 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: - `204` — The session ended. No response body. - `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` — Sticky session 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"} ``` ### End all sticky sessions `POST /api/v1/customer/services/{service_id}/proxy/sticky/purge` - Access: API key required · scope `services:write` End all sticky sessions of a rotating service. 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/proxy/sticky/purge" \ -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/proxy/sticky/purge", 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/proxy/sticky/purge", { 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 '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/proxy/sticky/purge", 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 ending all sticky sessions. Example response: ```json { "deleted": 0 } ``` | Field | Type | Required | Description | |---|---|---|---| | `deleted` | integer | 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 `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"} ``` ### Get session mode `GET /api/v1/customer/services/{service_id}/proxy/sticky/mode` - Access: API key required · scope `services:read` Current session mode of a rotating service: `rotating` (new exit IP per request), `sticky`, or `pinned`. 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/proxy/sticky/mode" \ -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/proxy/sticky/mode", 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/proxy/sticky/mode", { 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 '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/proxy/sticky/mode", 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` — Session mode: `rotating`, `sticky`, or `pinned`. Example response: ```json { "mode": "sticky" } ``` | Field | Type | Required | Description | |---|---|---|---| | `mode` | string | yes | | | `pin` | string, 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: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"} ``` ### Set session mode `PUT /api/v1/customer/services/{service_id}/proxy/sticky/mode` - Access: API key required · scope `services:write` Switch between `rotating`, `sticky`, and `pinned` modes. `pin` is required for `pinned`. If your rotating service doesn't support switching the mode, the response is `501`; use the sticky session duration endpoints instead. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `mode` | string | yes | sticky \| rotating \| pinned · One of: `sticky`, `rotating`, `pinned` | | `pin` | string, nullable | no | Exit IP:port to keep when `mode` is `pinned`. | Request examples: cURL: ```bash curl -X PUT "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/mode" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"mode": "sticky"}' ``` Python: ```python import os import requests resp = requests.put( "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/mode", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "mode": "sticky" }, 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/proxy/sticky/mode", { method: "PUT", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "mode": "sticky" }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'PUT', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'mode' => 'sticky' ]), ]); $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("PUT", "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/mode", strings.NewReader(`{"mode": "sticky"}`)) 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` — Session mode: `rotating`, `sticky`, or `pinned`. Example response: ```json { "mode": "sticky" } ``` | Field | Type | Required | Description | |---|---|---|---| | `mode` | string | yes | | | `pin` | string, 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."} ``` - `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"} ``` - `501` — Switching the session mode isn't supported for this service. Example: ```json {"detail": "Sticky mode is not supported by the configured upstream."} ``` ### Get sticky session duration `GET /api/v1/customer/services/{service_id}/proxy/sticky/ttl` - Access: API key required · scope `services:read` How long a sticky session keeps the same exit IP, in seconds. 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/proxy/sticky/ttl" \ -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/proxy/sticky/ttl", 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/proxy/sticky/ttl", { 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 '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/proxy/sticky/ttl", 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` — Sticky session duration, in seconds. Example response: ```json { "ttl_seconds": 900, "is_custom": false, "global_default_ttl": 900, "ttl_human": "15m" } ``` | Field | Type | Required | Description | |---|---|---|---| | `ttl_seconds` | integer, nullable | no | Sticky session duration, in seconds. | | `is_custom` | boolean | no | Default: `false` | | `global_default_ttl` | integer, nullable | no | | | `ttl_human` | string, 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: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"} ``` ### Set sticky session duration `PUT /api/v1/customer/services/{service_id}/proxy/sticky/ttl` - Access: API key required · scope `services:write` Set how long a sticky session keeps the same exit IP, in seconds (minimum 30). Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `seconds` | integer | yes | TTL override in seconds (minimum 30) · Minimum: 30 | Request examples: cURL: ```bash curl -X PUT "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/ttl" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"seconds": 600}' ``` Python: ```python import os import requests resp = requests.put( "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/ttl", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "seconds": 600 }, 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/proxy/sticky/ttl", { method: "PUT", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "seconds": 600 }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'PUT', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'seconds' => 600 ]), ]); $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("PUT", "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/ttl", strings.NewReader(`{"seconds": 600}`)) 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` — Sticky session duration, in seconds. Example response: ```json { "ttl_seconds": 600, "is_custom": true, "global_default_ttl": 900, "ttl_human": "10m" } ``` | Field | Type | Required | Description | |---|---|---|---| | `ttl_seconds` | integer, nullable | no | Sticky session duration, in seconds. | | `is_custom` | boolean | no | Default: `false` | | `global_default_ttl` | integer, nullable | no | | | `ttl_human` | string, 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."} ``` - `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"} ``` ### Reset sticky session duration `DELETE /api/v1/customer/services/{service_id}/proxy/sticky/ttl` - Access: API key required · scope `services:write` Return the sticky session duration to the default. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `service_id (path)` | string (uuid) | yes | ID of the service. | Request examples: cURL: ```bash curl -X DELETE "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/ttl" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.delete( "https://papaproxy.net/account_new/api/v1/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/ttl", 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/proxy/sticky/ttl", { 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 '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/customer/services/3fa85f64-5717-4562-b3fc-2c963f66afa6/proxy/sticky/ttl", 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` — Sticky session duration, in seconds. Example response: ```json { "ttl_seconds": 900, "is_custom": false, "global_default_ttl": 900, "ttl_human": "15m" } ``` | Field | Type | Required | Description | |---|---|---|---| | `ttl_seconds` | integer, nullable | no | Sticky session duration, in seconds. | | `is_custom` | boolean | no | Default: `false` | | `global_default_ttl` | integer, nullable | no | | | `ttl_human` | string, 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."} ``` - `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"} ``` --- # 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 '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 '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 '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 '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"} ``` --- # Wallet — PapaProxy.net API reference Page: https://papaproxy.net/developers/reference/wallet.php Last updated: November 9, 2026 Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/ ### Get wallet balance `GET /api/v1/customer/wallet/balance` - Access: API key required · scope `wallet:read` Your prepaid wallet balance, calculated from the transaction ledger. Accounts that have never topped up report `balance_cents=0`. Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/wallet/balance" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/customer/wallet/balance", 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/wallet/balance", { 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 '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/wallet/balance", 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` — Current wallet balance. Example response: ```json { "wallet_id": "d7fa9fe6-fd70-582d-aa84-e52c5068ca35", "owner_type": "customer", "owner_id": "8218fbc6-d67b-52fd-a639-cca399215d31", "balance_cents": 1997, "currency": "USD", "has_wallet": true } ``` | Field | Type | Required | Description | |---|---|---|---| | `wallet_id` | string (uuid), nullable | no | ID of your wallet. | | `owner_type` | string | yes | | | `owner_id` | string (uuid) | yes | | | `balance_cents` | integer | yes | Available balance in integer cents | | `currency` | string | no | Currency code (`USD`). · Min length: 3 · Max length: 3 · Default: `"USD"` | | `has_wallet` | boolean | no | False if the account has never topped up; `balance_cents` is 0 in that case. · Default: `true` | - `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 `wallet:read`. Example: ```json {"detail": "API key is missing required scope: wallet:read"} ``` - `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 wallet transactions `GET /api/v1/customer/wallet/ledger` - Access: API key required · scope `wallet:read` List wallet transactions (paginated, newest first). Entries linked to a specific service carry its `service_id`. 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: 200 · Default: `50` | Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/wallet/ledger" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/customer/wallet/ledger", 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/wallet/ledger", { 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 '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/wallet/ledger", 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 list of ledger entries. Example response: ```json { "entries": [ { "id": "d4ac48f1-e447-50a4-9b61-a95ea7505be0", "wallet_id": "d7fa9fe6-fd70-582d-aa84-e52c5068ca35", "idempotency_key": "addon_refund:fc9f2062-7726-5122-ab7f-d6a7814ed548#s1", "provider": null, "provider_tx_id": null, "amount_cents": 3, "balance_after_cents": 1997, "entry_type": "refund", "status": "completed", "description": "Addon refund: Extra connections (30 unused days) for Datacenter Proxy US 500 IPs", "service_id": "cb532178-5b03-506a-84ea-0230ff28576b", "usage_requests": null, "usage_bytes": null, "created_at": "2026-11-07T14:30:45.447818Z" }, { "id": "872ef948-7db3-5d67-9755-cc0f965b2b7a", "wallet_id": "d7fa9fe6-fd70-582d-aa84-e52c5068ca35", "idempotency_key": "addon_purchase:capture-d576b57ad59e", "provider": null, "provider_tx_id": null, "amount_cents": -3, "balance_after_cents": 1994, "entry_type": "debit", "status": "completed", "description": "Addon purchase: Extra connections for Datacenter Proxy US 500 IPs", "service_id": "cb532178-5b03-506a-84ea-0230ff28576b", "usage_requests": null, "usage_bytes": null, "created_at": "2026-11-07T14:30:43.459177Z" } ], "total": 26, "offset": 0, "limit": 3 } ``` | Field | Type | Required | Description | |---|---|---|---| | `entries` | array of object | yes | | | `entries[].id` | string (uuid) | yes | Unique ID. | | `entries[].wallet_id` | string (uuid) | yes | ID of your wallet. | | `entries[].idempotency_key` | string | yes | | | `entries[].provider` | string, nullable | yes | Payment processor or source of the transaction. | | `entries[].provider_tx_id` | string, nullable | yes | Transaction ID at the payment processor. | | `entries[].amount_cents` | integer | yes | Amount in integer cents | | `entries[].balance_after_cents` | integer | yes | Running balance after this entry | | `entries[].entry_type` | string | yes | | | `entries[].status` | string | yes | | | `entries[].description` | string, nullable | yes | | | `entries[].service_id` | string (uuid), nullable | yes | ID of the service. | | `entries[].usage_requests` | integer, nullable | yes | | | `entries[].usage_bytes` | integer, nullable | yes | | | `entries[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `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 `wallet:read`. Example: ```json {"detail": "API key is missing required scope: wallet: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"} ``` ### Top up your wallet `POST /api/v1/customer/wallet/deposit/init` - Access: API key required · scope `wallet:deposit` Start a wallet top-up and get a checkout URL. The wallet is credited after the payment processor confirms the payment — check the transaction list to see the credit. Pass `idempotency_key` to make retries safe. If the selected payment method requires identity verification that hasn't been completed, the response is HTTP 403 with `detail.error = "kyc_required"`. Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `gateway` | string | yes | Payment gateway: stripe \| heleket \| freekassa \| onepayment · Min length: 1 · Max length: 64 | | `amount_cents` | integer | yes | Amount in integer cents | | `idempotency_key` | string, nullable | no | Optional client-supplied idempotency key · Max length: 255 | | `currency` | string | no | Currency code (`USD`). · Min length: 3 · Max length: 3 · Default: `"USD"` | | `order_metadata` | object, nullable | no | Optional order details to attach to the payment. | | `return_url` | string, nullable | no | Relative path to return to after a canceled or failed payment (for example `/wallet`). Must start with `/`. · Max length: 2048 | | `success_url` | string, nullable | no | Relative path to return to after a successful payment. Must start with `/`. · Max length: 2048 | | `save_card` | boolean | no | Save the card for automatic top-ups (card payments via Stripe only). · Default: `false` | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/customer/wallet/deposit/init" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"amount_cents": 5000, "gateway": "heleket", "idempotency_key": "deposit-2026-001"}' ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/customer/wallet/deposit/init", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "amount_cents": 5000, "gateway": "heleket", "idempotency_key": "deposit-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/wallet/deposit/init", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "amount_cents": 5000, "gateway": "heleket", "idempotency_key": "deposit-2026-001" }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'amount_cents' => 5000, 'gateway' => 'heleket', 'idempotency_key' => 'deposit-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/wallet/deposit/init", strings.NewReader(`{"amount_cents": 5000, "gateway": "heleket", "idempotency_key": "deposit-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` — Top-up checkout details. Example response: ```json { "payment_url": "https://pay.example.com/invoice/675388be-0c31-539b-8e81-0272df8d2f24", "our_order_id": "HLK-8F2A61C04D9E7B35", "amount_cents": 5000, "currency": "USD", "provider_tx_id": null, "checkout_kind": "redirect", "allowed_payment_methods": null, "paypal_remaining_cents": null } ``` | Field | Type | Required | Description | |---|---|---|---| | `payment_url` | string | yes | | | `our_order_id` | string | yes | | | `amount_cents` | integer | yes | | | `currency` | string | no | Currency code (`USD`). · Default: `"USD"` | | `provider_tx_id` | string, nullable | no | | | `checkout_kind` | string | no | Default: `"redirect"` | | `allowed_payment_methods` | array of string, nullable | no | Payment methods allowed for this top-up, when restricted (for example `["card"]`). Null means no restriction. | | `paypal_remaining_cents` | integer, nullable | no | Remaining PayPal limit for this account, in cents. Null when there is no limit. | - `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 `wallet:deposit`, or the payment method requires identity verification (`detail.error = "kyc_required"`). - `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"} ``` ### Download an invoice (PDF) `GET /api/v1/customer/invoices/{invoice_id}/pdf` - Access: API key required · scope `wallet:read` Download the PDF of one of your invoices. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `invoice_id (path)` | string | yes | ID of the invoice. | Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/invoices/string/pdf" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/customer/invoices/string/pdf", 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/invoices/string/pdf", { 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 '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/invoices/string/pdf", 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` — The invoice PDF. - `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 `wallet:read`. Example: ```json {"detail": "API key is missing required scope: wallet:read"} ``` - `404` — Invoice 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"} ``` --- # Account — PapaProxy.net API reference Page: https://papaproxy.net/developers/reference/account.php Last updated: November 9, 2026 Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/ ### Get your profile `GET /api/v1/customer/me` - Access: API key required · scope `customer:read` Your account profile. Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/me" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/customer/me", 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/me", { 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 '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/me", 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` — Your profile. Example response: ```json { "customer": { "id": "8218fbc6-d67b-52fd-a639-cca399215d31", "email": "you@example.com", "name": "Alex Morgan", "phone": null, "is_verified": true, "kyc_status": null, "created_at": "2026-08-25T10:44:28.118221+00:00", "locale": "ru", "has_password": true, "oauth_provider": null, "account_type": "personal", "company_name": null, "address_line1": null, "address_line2": null, "city": null, "state": null, "postcode": null, "country": "US", "tax_id": null } } ``` | Field | Type | Required | Description | |---|---|---|---| | `customer` | object | yes | Your account profile. | | `customer.id` | string (uuid) | yes | Unique ID. | | `customer.email` | string | yes | Account email address. | | `customer.name` | string, nullable | yes | | | `customer.phone` | string, nullable | yes | Phone number, if set. | | `customer.is_verified` | boolean | yes | Whether the email address is verified. | | `customer.kyc_status` | string, nullable | yes | | | `customer.created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `customer.locale` | string, nullable | no | | | `customer.has_password` | boolean | no | Default: `true` | | `customer.oauth_provider` | string, nullable | no | Sign-in provider used for the account, if any. | | `customer.account_type` | string | no | Default: `"personal"` | | `customer.company_name` | string, nullable | no | | | `customer.address_line1` | string, nullable | no | | | `customer.address_line2` | string, nullable | no | | | `customer.city` | string, nullable | no | | | `customer.state` | string, nullable | no | | | `customer.postcode` | string, nullable | no | | | `customer.country` | string, nullable | no | Country from your profile, if set. | | `customer.tax_id` | string, nullable | no | | | `customer.partner_status` | string, nullable | no | Status of your affiliate partner profile, if any. | - `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 `customer:read`. Example: ```json {"detail": "API key is missing required scope: customer:read"} ``` - `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"} ``` ### Update your profile `PATCH /api/v1/customer/me` - Access: API key required · scope `customer:write` Update your name or phone number. Only `name` and `phone` are accepted; any other field, including `email` and `locale`, returns `422`. Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `name` | string, nullable | no | Max length: 255 | | `phone` | string, nullable | no | Phone number, if set. · Max length: 30 | Request examples: cURL: ```bash curl -X PATCH "https://papaproxy.net/account_new/api/v1/customer/me" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "ACME Ops"}' ``` Python: ```python import os import requests resp = requests.patch( "https://papaproxy.net/account_new/api/v1/customer/me", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "name": "ACME Ops" }, timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/me", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "name": "ACME Ops" }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'PATCH', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'name' => 'ACME Ops' ]), ]); $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("PATCH", "https://papaproxy.net/account_new/api/v1/customer/me", strings.NewReader(`{"name": "ACME Ops"}`)) 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` — Full profile of the currently authenticated customer. Example response: ```json { "customer": { "id": "8218fbc6-d67b-52fd-a639-cca399215d31", "email": "you@example.com", "name": "Alex Morgan", "phone": null, "is_verified": true, "kyc_status": null, "created_at": "2026-08-25T10:44:28.118221+00:00", "locale": "ru", "has_password": true, "oauth_provider": null, "account_type": "personal", "company_name": null, "address_line1": null, "address_line2": null, "city": null, "state": null, "postcode": null, "country": "US", "tax_id": null } } ``` | Field | Type | Required | Description | |---|---|---|---| | `customer` | object | yes | Your account profile. | | `customer.id` | string (uuid) | yes | Unique ID. | | `customer.email` | string | yes | Account email address. | | `customer.name` | string, nullable | yes | | | `customer.phone` | string, nullable | yes | Phone number, if set. | | `customer.is_verified` | boolean | yes | Whether the email address is verified. | | `customer.kyc_status` | string, nullable | yes | | | `customer.created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `customer.locale` | string, nullable | no | | | `customer.has_password` | boolean | no | Default: `true` | | `customer.oauth_provider` | string, nullable | no | Sign-in provider used for the account, if any. | | `customer.account_type` | string | no | Default: `"personal"` | | `customer.company_name` | string, nullable | no | | | `customer.address_line1` | string, nullable | no | | | `customer.address_line2` | string, nullable | no | | | `customer.city` | string, nullable | no | | | `customer.state` | string, nullable | no | | | `customer.postcode` | string, nullable | no | | | `customer.country` | string, nullable | no | Country from your profile, if set. | | `customer.tax_id` | string, nullable | no | | | `customer.partner_status` | string, nullable | no | Status of your affiliate partner profile, if any. | - `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 `customer:write`. Example: ```json {"detail": "API key is missing required scope: customer: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"} ``` --- # API keys — PapaProxy.net API reference Page: https://papaproxy.net/developers/reference/api-keys.php Last updated: November 9, 2026 Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/ ### List API keys `GET /api/v1/customers/me/api-keys` - Access: API key required List your API keys. Works only in a signed-in Dashboard session; a request made with an API key returns `403`. Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customers/me/api-keys" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/customers/me/api-keys", 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/customers/me/api-keys", { 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 '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/customers/me/api-keys", 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` — Your API keys (the secret itself is never returned again). Example response: ```json [ { "id": "ab1deea7-362b-5dd5-83b7-0a58f0c819a0", "name": "ci-deploy", "key_prefix": "fb_4f1c9a7e2", "scopes": [], "is_active": false, "created_at": "2026-11-07T14:14:53.656051Z", "last_used_at": "2026-11-07T14:14:55.980597Z", "last_used_ip": "198.51.100.24", "expires_at": null }, { "id": "29066e86-06b5-5c5f-9576-519cf1dc2fb8", "name": "ci-deploy", "key_prefix": "fb_4f1c9a7e2", "scopes": [], "is_active": false, "created_at": "2026-11-07T14:09:29.016284Z", "last_used_at": null, "last_used_ip": null, "expires_at": null } ] ``` | Field | Type | Required | Description | |---|---|---|---| | `[].id` | string (uuid) | yes | Unique ID. | | `[].name` | string | yes | | | `[].key_prefix` | string | yes | First characters of the key, to tell keys apart. | | `[].scopes` | array of string | yes | Scopes granted to the key. | | `[].is_active` | boolean | yes | Whether it's active. | | `[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `[].last_used_at` | string (date-time), nullable | no | When the key was last used (ISO 8601, UTC). | | `[].last_used_ip` | string, nullable | no | IP address the key was last used from. | | `[].expires_at` | string (date-time), nullable | no | When the current term ends (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."} ``` - `403` — The request was made with an API key. Manage keys in the Dashboard. Example: ```json {"detail": "api_key_management_requires_session"} ``` - `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"} ``` ### Create an API key `POST /api/v1/customers/me/api-keys` - Access: API key required Create an API key with the given scopes. The secret is returned only once, in this response, so store it securely. Works only in a signed-in Dashboard session; a request made with an API key returns `403`. Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `name` | string | yes | Min length: 1 · Max length: 255 | | `scopes` | array of string | yes | Scopes granted to this key. · Min items: 1 | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/customers/me/api-keys" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "my-key", "scopes": ["services:read", "wallet:read"]}' ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/customers/me/api-keys", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "name": "my-key", "scopes": [ "services:read", "wallet:read" ] }, timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/customers/me/api-keys", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "name": "my-key", "scopes": [ "services:read", "wallet:read" ] }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'name' => 'my-key', 'scopes' => ['services:read', 'wallet:read'] ]), ]); $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/customers/me/api-keys", strings.NewReader(`{"name": "my-key", "scopes": ["services:read", "wallet:read"]}`)) 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` — Returned once on creation — includes the raw key (shown only here). Example response: ```json { "id": "3aeee9b3-41a1-5a66-a735-a475b0aebe55", "name": "ci-deploy", "key_prefix": "fb_4f1c9a7e2", "scopes": [ "services:read" ], "is_active": true, "created_at": "2026-11-07T14:31:35.953143Z", "last_used_at": null, "last_used_ip": null, "expires_at": null, "api_key": "fb_4f1c9a7e2b6d80534f1c9a7e2b6d80534f1c9a7e2b6d80534f1c9a7e2b6d8053" } ``` | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Unique ID. | | `name` | string | yes | | | `key_prefix` | string | yes | First characters of the key, to tell keys apart. | | `scopes` | array of string | yes | Scopes granted to the key. | | `is_active` | boolean | yes | Whether it's active. | | `created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `last_used_at` | string (date-time), nullable | no | When the key was last used (ISO 8601, UTC). | | `last_used_ip` | string, nullable | no | IP address the key was last used from. | | `expires_at` | string (date-time), nullable | no | When the current term ends (ISO 8601, UTC). | | `api_key` | string | yes | Full secret key — shown ONCE, store it 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 request was made with an API key. Manage keys in the Dashboard. Example: ```json {"detail": "api_key_management_requires_session"} ``` - `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"} ``` ### Revoke an API key `DELETE /api/v1/customers/me/api-keys/{key_id}` - Access: API key required Revoke one of your API keys. Requests made with it fail immediately afterward. Works only in a signed-in Dashboard session; a request made with an API key returns `403`. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `key_id (path)` | string (uuid) | yes | ID of the api key. | Request examples: cURL: ```bash curl -X DELETE "https://papaproxy.net/account_new/api/v1/customers/me/api-keys/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/customers/me/api-keys/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/customers/me/api-keys/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 '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/customers/me/api-keys/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` — Generic message response. Example response: ```json { "message": "API key revoked." } ``` | Field | Type | Required | Description | |---|---|---|---| | `message` | string | 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 request was made with an API key. Manage keys in the Dashboard. Example: ```json {"detail": "api_key_management_requires_session"} ``` - `404` — API key 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"} ``` --- # Support tickets — PapaProxy.net API reference Page: https://papaproxy.net/developers/reference/support-tickets.php Last updated: November 9, 2026 Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/ ### List support tickets `GET /api/v1/customer/tickets` - Access: API key required · scope `tickets:read` List your support tickets, newest first. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `offset (query)` | integer | no | Number of items to skip. · Default: `0` | | `limit (query)` | integer | no | Page size. · Default: `50` | Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/tickets" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/customer/tickets", 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/tickets", { 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 '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/tickets", 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 ticket list. Example response: ```json { "tickets": [ { "id": "25d98664-5d50-59ee-974a-d7dd4773bde9", "customer_id": "8218fbc6-d67b-52fd-a639-cca399215d31", "subject": "Proxy question", "status": "closed", "service_id": null, "last_message_at": "2026-11-07T14:09:52.876903Z", "created_at": "2026-11-07T14:09:52.876903Z", "updated_at": "2026-11-07T14:24:19.868992Z" } ], "total": 11, "offset": 0, "limit": 1 } ``` | Field | Type | Required | Description | |---|---|---|---| | `tickets` | array of object | yes | | | `tickets[].id` | string (uuid) | yes | Unique ID. | | `tickets[].customer_id` | string (uuid), nullable | yes | ID of your account. | | `tickets[].subject` | string | yes | Ticket subject. | | `tickets[].status` | string | yes | Ticket status. · One of: `open`, `in_progress`, `waiting`, `customer_replied`, `closed` | | `tickets[].service_id` | string (uuid), nullable | yes | ID of the service. | | `tickets[].last_message_at` | string (date-time), nullable | yes | When the last message was posted (ISO 8601, UTC). | | `tickets[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `tickets[].updated_at` | string (date-time), nullable | yes | When it was last changed (ISO 8601, UTC). | | `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 `tickets:read`. Example: ```json {"detail": "API key is missing required scope: tickets: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"} ``` ### Open a support ticket `POST /api/v1/customer/tickets` - Access: API key required · scope `tickets:write` Create a new support ticket. Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `subject` | string | yes | Ticket subject. · Min length: 1 · Max length: 500 | | `message` | string | yes | Min length: 1 · Max length: 10000 | | `service_id` | string (uuid), nullable | no | ID of the service. | | `attachments` | array of object | no | Attached files. · Max items: 5 | | `attachments[].filename` | string | yes | File name. · Min length: 1 · Max length: 255 | | `attachments[].content_type` | string | yes | MIME type of the file. · Min length: 1 · Max length: 127 | | `attachments[].data_base64` | string | yes | Min length: 1 · Max length: 28000000 | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/customer/tickets" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"subject": "Proxy question", "message": "Two IPs in my US package time out on HTTPS. Could you check them?"}' ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/customer/tickets", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "subject": "Proxy question", "message": "Two IPs in my US package time out on HTTPS. Could you check them?" }, timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/tickets", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "subject": "Proxy question", "message": "Two IPs in my US package time out on HTTPS. Could you check them?" }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'subject' => 'Proxy question', 'message' => 'Two IPs in my US package time out on HTTPS. Could you check them?' ]), ]); $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/tickets", strings.NewReader(`{"subject": "Proxy question", "message": "Two IPs in my US package time out on HTTPS. Could you check them?"}`)) 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` — Ticket with full message history. Example response: ```json { "id": "25d98664-5d50-59ee-974a-d7dd4773bde9", "customer_id": "8218fbc6-d67b-52fd-a639-cca399215d31", "subject": "Proxy question", "status": "closed", "service_id": null, "last_message_at": "2026-11-07T14:09:52.876903Z", "created_at": "2026-11-07T14:09:52.876903Z", "updated_at": "2026-11-07T14:24:19.868992Z", "messages": [ { "id": "101deaaa-d993-5b5a-8826-13a213959f27", "ticket_id": "25d98664-5d50-59ee-974a-d7dd4773bde9", "author_kind": "customer", "author_id": "8218fbc6-d67b-52fd-a639-cca399215d31", "message": "Hi! Two IPs in my US package time out on HTTPS. Could you check them?", "created_at": "2026-11-07T14:09:52.876903Z", "attachments": [], "detected_lang": null, "translated_body": null, "translation_target": null } ] } ``` | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Unique ID. | | `customer_id` | string (uuid), nullable | yes | ID of your account. | | `subject` | string | yes | Ticket subject. | | `status` | string | yes | Ticket status. · One of: `open`, `in_progress`, `waiting`, `customer_replied`, `closed` | | `service_id` | string (uuid), nullable | yes | ID of the service. | | `last_message_at` | string (date-time), nullable | yes | When the last message was posted (ISO 8601, UTC). | | `created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `updated_at` | string (date-time), nullable | yes | When it was last changed (ISO 8601, UTC). | | `messages` | array of object | yes | | | `messages[].id` | string (uuid) | yes | Unique ID. | | `messages[].ticket_id` | string (uuid) | yes | | | `messages[].author_kind` | string | yes | | | `messages[].author_id` | string (uuid), nullable | yes | | | `messages[].message` | string | yes | | | `messages[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `messages[].attachments` | array of object | no | Attached files. | | `messages[].attachments[].id` | string (uuid) | yes | Unique ID. | | `messages[].attachments[].message_id` | string (uuid) | yes | | | `messages[].attachments[].uploader_type` | string | yes | | | `messages[].attachments[].filename` | string | yes | File name. | | `messages[].attachments[].content_type` | string | yes | MIME type of the file. | | `messages[].attachments[].size` | integer | yes | | | `messages[].attachments[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `messages[].detected_lang` | string, nullable | no | | | `messages[].translated_body` | string, nullable | no | | | `messages[].translation_target` | string, 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 `tickets:write`. Example: ```json {"detail": "API key is missing required scope: tickets: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 support ticket `GET /api/v1/customer/tickets/{ticket_id}` - Access: API key required · scope `tickets:read` Get a single ticket with its full message history. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `ticket_id (path)` | string (uuid) | yes | ID of the ticket. | Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/tickets/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/tickets/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/tickets/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 '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/tickets/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` — Ticket with full message history. Example response: ```json { "id": "25d98664-5d50-59ee-974a-d7dd4773bde9", "customer_id": "8218fbc6-d67b-52fd-a639-cca399215d31", "subject": "Proxy question", "status": "closed", "service_id": null, "last_message_at": "2026-11-07T14:09:52.876903Z", "created_at": "2026-11-07T14:09:52.876903Z", "updated_at": "2026-11-07T14:24:19.868992Z", "messages": [ { "id": "101deaaa-d993-5b5a-8826-13a213959f27", "ticket_id": "25d98664-5d50-59ee-974a-d7dd4773bde9", "author_kind": "customer", "author_id": "8218fbc6-d67b-52fd-a639-cca399215d31", "message": "Hi! Two IPs in my US package time out on HTTPS. Could you check them?", "created_at": "2026-11-07T14:09:52.876903Z", "attachments": [], "detected_lang": null, "translated_body": null, "translation_target": null } ] } ``` | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Unique ID. | | `customer_id` | string (uuid), nullable | yes | ID of your account. | | `subject` | string | yes | Ticket subject. | | `status` | string | yes | Ticket status. · One of: `open`, `in_progress`, `waiting`, `customer_replied`, `closed` | | `service_id` | string (uuid), nullable | yes | ID of the service. | | `last_message_at` | string (date-time), nullable | yes | When the last message was posted (ISO 8601, UTC). | | `created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `updated_at` | string (date-time), nullable | yes | When it was last changed (ISO 8601, UTC). | | `messages` | array of object | yes | | | `messages[].id` | string (uuid) | yes | Unique ID. | | `messages[].ticket_id` | string (uuid) | yes | | | `messages[].author_kind` | string | yes | | | `messages[].author_id` | string (uuid), nullable | yes | | | `messages[].message` | string | yes | | | `messages[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `messages[].attachments` | array of object | no | Attached files. | | `messages[].attachments[].id` | string (uuid) | yes | Unique ID. | | `messages[].attachments[].message_id` | string (uuid) | yes | | | `messages[].attachments[].uploader_type` | string | yes | | | `messages[].attachments[].filename` | string | yes | File name. | | `messages[].attachments[].content_type` | string | yes | MIME type of the file. | | `messages[].attachments[].size` | integer | yes | | | `messages[].attachments[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `messages[].detected_lang` | string, nullable | no | | | `messages[].translated_body` | string, nullable | no | | | `messages[].translation_target` | string, 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 `tickets:read`. Example: ```json {"detail": "API key is missing required scope: tickets:read"} ``` - `404` — Ticket 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"} ``` ### Reply to a support ticket `POST /api/v1/customer/tickets/{ticket_id}/reply` - Access: API key required · scope `tickets:write` Reply to an existing ticket. Closed tickets can't receive replies: the response is `400` (`Cannot reply to a closed ticket. Reopen it first.`). Parameters: | Field | Type | Required | Description | |---|---|---|---| | `ticket_id (path)` | string (uuid) | yes | ID of the ticket. | Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `message` | string | yes | Min length: 1 · Max length: 10000 | | `attachments` | array of object | no | Attached files. · Max items: 5 | | `attachments[].filename` | string | yes | File name. · Min length: 1 · Max length: 255 | | `attachments[].content_type` | string | yes | MIME type of the file. · Min length: 1 · Max length: 127 | | `attachments[].data_base64` | string | yes | Min length: 1 · Max length: 28000000 | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/customer/tickets/3fa85f64-5717-4562-b3fc-2c963f66afa6/reply" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"message": "Thanks, both IPs work now."}' ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/customer/tickets/3fa85f64-5717-4562-b3fc-2c963f66afa6/reply", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "message": "Thanks, both IPs work now." }, timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/tickets/3fa85f64-5717-4562-b3fc-2c963f66afa6/reply", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "message": "Thanks, both IPs work now." }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'message' => 'Thanks, both IPs work now.' ]), ]); $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/tickets/3fa85f64-5717-4562-b3fc-2c963f66afa6/reply", strings.NewReader(`{"message": "Thanks, both IPs work now."}`)) 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` — Ticket with full message history. Example response: ```json { "id": "25d98664-5d50-59ee-974a-d7dd4773bde9", "customer_id": "8218fbc6-d67b-52fd-a639-cca399215d31", "subject": "Proxy question", "status": "closed", "service_id": null, "last_message_at": "2026-11-07T14:09:52.876903Z", "created_at": "2026-11-07T14:09:52.876903Z", "updated_at": "2026-11-07T14:24:19.868992Z", "messages": [ { "id": "101deaaa-d993-5b5a-8826-13a213959f27", "ticket_id": "25d98664-5d50-59ee-974a-d7dd4773bde9", "author_kind": "customer", "author_id": "8218fbc6-d67b-52fd-a639-cca399215d31", "message": "Hi! Two IPs in my US package time out on HTTPS. Could you check them?", "created_at": "2026-11-07T14:09:52.876903Z", "attachments": [], "detected_lang": null, "translated_body": null, "translation_target": null } ] } ``` | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Unique ID. | | `customer_id` | string (uuid), nullable | yes | ID of your account. | | `subject` | string | yes | Ticket subject. | | `status` | string | yes | Ticket status. · One of: `open`, `in_progress`, `waiting`, `customer_replied`, `closed` | | `service_id` | string (uuid), nullable | yes | ID of the service. | | `last_message_at` | string (date-time), nullable | yes | When the last message was posted (ISO 8601, UTC). | | `created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `updated_at` | string (date-time), nullable | yes | When it was last changed (ISO 8601, UTC). | | `messages` | array of object | yes | | | `messages[].id` | string (uuid) | yes | Unique ID. | | `messages[].ticket_id` | string (uuid) | yes | | | `messages[].author_kind` | string | yes | | | `messages[].author_id` | string (uuid), nullable | yes | | | `messages[].message` | string | yes | | | `messages[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `messages[].attachments` | array of object | no | Attached files. | | `messages[].attachments[].id` | string (uuid) | yes | Unique ID. | | `messages[].attachments[].message_id` | string (uuid) | yes | | | `messages[].attachments[].uploader_type` | string | yes | | | `messages[].attachments[].filename` | string | yes | File name. | | `messages[].attachments[].content_type` | string | yes | MIME type of the file. | | `messages[].attachments[].size` | integer | yes | | | `messages[].attachments[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). | | `messages[].detected_lang` | string, nullable | no | | | `messages[].translated_body` | string, nullable | no | | | `messages[].translation_target` | string, nullable | no | | - `400` — The ticket is closed. Example: ```json {"detail": "Cannot reply to a closed ticket. Reopen it first."} ``` - `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 `tickets:write`. Example: ```json {"detail": "API key is missing required scope: tickets:write"} ``` - `404` — Ticket 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"} ``` --- # Webhooks — PapaProxy.net API reference Page: https://papaproxy.net/developers/reference/webhooks.php Last updated: November 9, 2026 Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/ ### List webhook event types `GET /api/v1/customer/webhooks/events` - Access: API key required · scope `webhooks` Event types you can subscribe a webhook endpoint to. Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/webhooks/events" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/customer/webhooks/events", 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/webhooks/events", { 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 '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/webhooks/events", 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` — Event types you can subscribe to. Example response: ```json { "events": [ "deposit.completed", "payment.failed", "payment.succeeded", "service.expiring", "service.provisioned", "service.renewed", "service.suspended", "service.terminated", "wallet.balance_low" ] } ``` | Field | Type | Required | Description | |---|---|---|---| | `events` | array of string | yes | Event types the endpoint is subscribed to. | - `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 `webhooks`. Example: ```json {"detail": "API key is missing required scope: webhooks"} ``` - `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 webhook endpoints `GET /api/v1/customer/webhooks` - Access: API key required · scope `webhooks` List your webhook endpoints. Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/webhooks" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/customer/webhooks", 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/webhooks", { 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 '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/webhooks", 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` — Your webhook endpoints. Example response: ```json [ { "id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7", "url": "https://example.com/webhooks/papaproxy", "secret_prefix": "whsec_3p", "events": [ "service.provisioned", "wallet.balance_low" ], "description": null, "is_active": true, "disabled_reason": null, "consecutive_failures": 0, "created_at": "2026-11-07T14:31:39.000403Z", "updated_at": null } ] ``` | Field | Type | Required | Description | |---|---|---|---| | `[].id` | string (uuid) | yes | Unique ID. | | `[].url` | string | yes | | | `[].secret_prefix` | string | yes | First characters of the signing secret. | | `[].events` | array of string | yes | Event types the endpoint is subscribed to. | | `[].description` | string, nullable | no | | | `[].is_active` | boolean | yes | Whether it's active. | | `[].disabled_reason` | string, nullable | no | Why the endpoint was disabled, if it was. | | `[].consecutive_failures` | integer | no | Failed deliveries in a row. · Default: `0` | | `[].created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). | | `[].updated_at` | string (date-time), nullable | no | When it was last changed (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."} ``` - `403` — The API key lacks scope `webhooks`. Example: ```json {"detail": "API key is missing required scope: webhooks"} ``` - `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"} ``` ### Create a webhook endpoint `POST /api/v1/customer/webhooks` - Access: API key required · scope `webhooks` Register an HTTPS URL to receive event notifications. The signing secret (`whsec_…`) is returned only once, in this response: store it to verify the `X-FB-Signature` header of each delivery (see Webhooks in the overview). Only `https` URLs are accepted; an `http` URL returns `422` (`Only https URLs are allowed`). Every value in `events` must come from `GET /api/v1/customer/webhooks/events`; an unknown event type returns `422`. Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `url` | string | yes | Min length: 8 · Max length: 2048 | | `events` | array of string | yes | Event types the endpoint is subscribed to. · Min items: 1 | | `description` | string, nullable | no | Max length: 255 | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/customer/webhooks" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/webhooks/papaproxy", "events": ["service.provisioned", "wallet.balance_low"]}' ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/customer/webhooks", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "url": "https://example.com/webhooks/papaproxy", "events": [ "service.provisioned", "wallet.balance_low" ] }, timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "url": "https://example.com/webhooks/papaproxy", "events": [ "service.provisioned", "wallet.balance_low" ] }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'url' => 'https://example.com/webhooks/papaproxy', 'events' => ['service.provisioned', 'wallet.balance_low'] ]), ]); $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/webhooks", strings.NewReader(`{"url": "https://example.com/webhooks/papaproxy", "events": ["service.provisioned", "wallet.balance_low"]}`)) 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` — Create response — includes the raw secret exactly once. Example response: ```json { "id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7", "url": "https://example.com/webhooks/papaproxy", "secret_prefix": "whsec_3p", "events": [ "service.provisioned", "wallet.balance_low" ], "description": null, "is_active": true, "disabled_reason": null, "consecutive_failures": 0, "created_at": "2026-11-07T14:31:39.000403Z", "updated_at": null, "secret": "whsec_3pQ9xVn2LmA7cR8sK1tY5bZ0wE4uH6jD" } ``` | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Unique ID. | | `url` | string | yes | | | `secret_prefix` | string | yes | First characters of the signing secret. | | `events` | array of string | yes | Event types the endpoint is subscribed to. | | `description` | string, nullable | no | | | `is_active` | boolean | yes | Whether it's active. | | `disabled_reason` | string, nullable | no | Why the endpoint was disabled, if it was. | | `consecutive_failures` | integer | no | Failed deliveries in a row. · Default: `0` | | `created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). | | `updated_at` | string (date-time), nullable | no | When it was last changed (ISO 8601, UTC). | | `secret` | string | 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 `webhooks`. Example: ```json {"detail": "API key is missing required scope: webhooks"} ``` - `422` — Validation error, an `http` URL, or an unknown event type. Example: ```json {"detail": "Only https URLs are allowed"} ``` - `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 webhook endpoint `GET /api/v1/customer/webhooks/{endpoint_id}` - Access: API key required · scope `webhooks` Get one of your webhook endpoints. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `endpoint_id (path)` | string (uuid) | yes | ID of the webhook endpoint. | Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/webhooks/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/webhooks/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/webhooks/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 '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/webhooks/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` — The webhook endpoint. Example response: ```json { "id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7", "url": "https://example.com/webhooks/papaproxy", "secret_prefix": "whsec_3p", "events": [ "service.provisioned", "wallet.balance_low" ], "description": null, "is_active": true, "disabled_reason": null, "consecutive_failures": 0, "created_at": "2026-11-07T14:31:39.000403Z", "updated_at": null } ``` | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Unique ID. | | `url` | string | yes | | | `secret_prefix` | string | yes | First characters of the signing secret. | | `events` | array of string | yes | Event types the endpoint is subscribed to. | | `description` | string, nullable | no | | | `is_active` | boolean | yes | Whether it's active. | | `disabled_reason` | string, nullable | no | Why the endpoint was disabled, if it was. | | `consecutive_failures` | integer | no | Failed deliveries in a row. · Default: `0` | | `created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). | | `updated_at` | string (date-time), nullable | no | When it was last changed (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."} ``` - `403` — The API key lacks scope `webhooks`. Example: ```json {"detail": "API key is missing required scope: webhooks"} ``` - `404` — Webhook endpoint 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"} ``` ### Update a webhook endpoint `PATCH /api/v1/customer/webhooks/{endpoint_id}` - Access: API key required · scope `webhooks` Change the URL, event types, or status of a webhook endpoint. The same rules as on creation apply: `https` URLs only, and event types from `GET /api/v1/customer/webhooks/events`. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `endpoint_id (path)` | string (uuid) | yes | ID of the webhook endpoint. | Request body (JSON): | Field | Type | Required | Description | |---|---|---|---| | `url` | string, nullable | no | Min length: 8 · Max length: 2048 | | `events` | array of string, nullable | no | Event types the endpoint is subscribed to. · Min items: 1 | | `description` | string, nullable | no | Max length: 255 | | `is_active` | boolean, nullable | no | Whether it's active. | Request examples: cURL: ```bash curl -X PATCH "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/webhooks/papaproxy", "events": ["service.provisioned"]}' ``` Python: ```python import os import requests resp = requests.patch( "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6", headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"}, json={ "url": "https://example.com/webhooks/papaproxy", "events": [ "service.provisioned" ] }, timeout=30, ) resp.raise_for_status() print(resp.json()) ``` Node.js: ```javascript const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "url": "https://example.com/webhooks/papaproxy", "events": [ "service.provisioned" ] }), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); console.log(await resp.json()); ``` PHP: ```php 'PATCH', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'url' => 'https://example.com/webhooks/papaproxy', 'events' => ['service.provisioned'] ]), ]); $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("PATCH", "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6", strings.NewReader(`{"url": "https://example.com/webhooks/papaproxy", "events": ["service.provisioned"]}`)) 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` — The updated webhook endpoint. Example response: ```json { "id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7", "url": "https://example.com/webhooks/papaproxy", "secret_prefix": "whsec_3p", "events": [ "service.provisioned" ], "description": null, "is_active": true, "disabled_reason": null, "consecutive_failures": 0, "created_at": "2026-11-07T14:31:39.000403Z", "updated_at": "2026-11-07T14:31:41.801663Z" } ``` | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Unique ID. | | `url` | string | yes | | | `secret_prefix` | string | yes | First characters of the signing secret. | | `events` | array of string | yes | Event types the endpoint is subscribed to. | | `description` | string, nullable | no | | | `is_active` | boolean | yes | Whether it's active. | | `disabled_reason` | string, nullable | no | Why the endpoint was disabled, if it was. | | `consecutive_failures` | integer | no | Failed deliveries in a row. · Default: `0` | | `created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). | | `updated_at` | string (date-time), nullable | no | When it was last changed (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."} ``` - `403` — The API key lacks scope `webhooks`. Example: ```json {"detail": "API key is missing required scope: webhooks"} ``` - `404` — Webhook endpoint not found, or it doesn't belong to your account. - `422` — Validation error, an `http` URL, or an unknown event type. Example: ```json {"detail": "Only https URLs are allowed"} ``` - `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"} ``` ### Delete a webhook endpoint `DELETE /api/v1/customer/webhooks/{endpoint_id}` - Access: API key required · scope `webhooks` Delete a webhook endpoint. Deliveries to it stop immediately. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `endpoint_id (path)` | string (uuid) | yes | ID of the webhook endpoint. | Request examples: cURL: ```bash curl -X DELETE "https://papaproxy.net/account_new/api/v1/customer/webhooks/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/customer/webhooks/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/webhooks/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 '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/customer/webhooks/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` — Generic message response. Example response: ```json { "message": "ok" } ``` | Field | Type | Required | Description | |---|---|---|---| | `message` | string | 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 `webhooks`. Example: ```json {"detail": "API key is missing required scope: webhooks"} ``` - `404` — Webhook endpoint 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"} ``` ### List webhook deliveries `GET /api/v1/customer/webhooks/{endpoint_id}/deliveries` - Access: API key required · scope `webhooks` Recent delivery attempts for this endpoint, with response status codes. A failed delivery is retried 5 more times over about 8.5 hours, then marked as failed. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `endpoint_id (path)` | string (uuid) | yes | ID of the webhook endpoint. | | `limit (query)` | integer | no | Page size. · Minimum: 1 · Maximum: 50 · Default: `50` | Request examples: cURL: ```bash curl -X GET "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/deliveries" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.get( "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/deliveries", 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/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/deliveries", { 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 '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/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/deliveries", 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` — Recent deliveries, newest first. Example response: ```json [ { "id": "f2ec0963-48bb-52a5-966b-07b7e36a835d", "event_id": "1529f6ec-af23-594f-941d-a694f3dde477", "endpoint_id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7", "event_type": "ping", "status": "pending", "attempts": 0, "next_attempt_at": "2026-11-07T14:31:42.661857Z", "last_response_code": null, "last_response_body": null, "last_error": null, "created_at": "2026-11-07T14:31:42.656390Z", "updated_at": null } ] ``` | Field | Type | Required | Description | |---|---|---|---| | `[].id` | string (uuid) | yes | Unique ID. | | `[].event_id` | string (uuid) | yes | | | `[].endpoint_id` | string (uuid) | yes | | | `[].event_type` | string | yes | | | `[].status` | string | yes | | | `[].attempts` | integer | yes | | | `[].next_attempt_at` | string (date-time), nullable | no | | | `[].last_response_code` | integer, nullable | no | | | `[].last_response_body` | string, nullable | no | | | `[].last_error` | string, nullable | no | | | `[].created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). | | `[].updated_at` | string (date-time), nullable | no | When it was last changed (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."} ``` - `403` — The API key lacks scope `webhooks`. Example: ```json {"detail": "API key is missing required scope: webhooks"} ``` - `404` — Webhook endpoint 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"} ``` ### Send a test event `POST /api/v1/customer/webhooks/{endpoint_id}/test` - Access: API key required · scope `webhooks` Send a `ping` test event to this endpoint to check that it receives and verifies deliveries. The delivery is queued and usually arrives within a minute. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `endpoint_id (path)` | string (uuid) | yes | ID of the webhook endpoint. | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/test" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/test", 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/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/test", { 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 '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/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/test", 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: - `201` — The test delivery that was created. Example response: ```json { "id": "f2ec0963-48bb-52a5-966b-07b7e36a835d", "event_id": "1529f6ec-af23-594f-941d-a694f3dde477", "endpoint_id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7", "event_type": "ping", "status": "pending", "attempts": 0, "next_attempt_at": "2026-11-07T14:31:42.661857Z", "last_response_code": null, "last_response_body": null, "last_error": null, "created_at": "2026-11-07T14:31:42.656390Z", "updated_at": null } ``` | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Unique ID. | | `event_id` | string (uuid) | yes | | | `endpoint_id` | string (uuid) | yes | | | `event_type` | string | yes | | | `status` | string | yes | | | `attempts` | integer | yes | | | `next_attempt_at` | string (date-time), nullable | no | | | `last_response_code` | integer, nullable | no | | | `last_response_body` | string, nullable | no | | | `last_error` | string, nullable | no | | | `created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). | | `updated_at` | string (date-time), nullable | no | When it was last changed (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."} ``` - `403` — The API key lacks scope `webhooks`. Example: ```json {"detail": "API key is missing required scope: webhooks"} ``` - `404` — Webhook endpoint 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"} ``` ### Redeliver an event `POST /api/v1/customer/webhooks/deliveries/{delivery_id}/redeliver` - Access: API key required · scope `webhooks` Send a past delivery again, for example after fixing your endpoint. The event ID stays the same, so your handler can skip events it has already processed. Parameters: | Field | Type | Required | Description | |---|---|---|---| | `delivery_id (path)` | string (uuid) | yes | ID of the delivery. | Request examples: cURL: ```bash curl -X POST "https://papaproxy.net/account_new/api/v1/customer/webhooks/deliveries/3fa85f64-5717-4562-b3fc-2c963f66afa6/redeliver" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY" ``` Python: ```python import os import requests resp = requests.post( "https://papaproxy.net/account_new/api/v1/customer/webhooks/deliveries/3fa85f64-5717-4562-b3fc-2c963f66afa6/redeliver", 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/webhooks/deliveries/3fa85f64-5717-4562-b3fc-2c963f66afa6/redeliver", { 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 '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/webhooks/deliveries/3fa85f64-5717-4562-b3fc-2c963f66afa6/redeliver", 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: - `201` — The new delivery attempt. Example response: ```json { "id": "fcf4a431-710d-5edd-a55c-9a4efc65da19", "event_id": "3db339fc-c187-5608-b68a-7ee93c944424", "endpoint_id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7", "event_type": "ping", "status": "pending", "attempts": 0, "next_attempt_at": "2026-11-07T14:31:44.554104Z", "last_response_code": null, "last_response_body": null, "last_error": null, "created_at": "2026-11-07T14:31:44.548818Z", "updated_at": null } ``` | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Unique ID. | | `event_id` | string (uuid) | yes | | | `endpoint_id` | string (uuid) | yes | | | `event_type` | string | yes | | | `status` | string | yes | | | `attempts` | integer | yes | | | `next_attempt_at` | string (date-time), nullable | no | | | `last_response_code` | integer, nullable | no | | | `last_response_body` | string, nullable | no | | | `last_error` | string, nullable | no | | | `created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). | | `updated_at` | string (date-time), nullable | no | When it was last changed (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."} ``` - `403` — The API key lacks scope `webhooks`. Example: ```json {"detail": "API key is missing required scope: webhooks"} ``` - `404` — Delivery 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"} ```