Catalog and pricing
6 endpoints · base URL https://papaproxy.net/account_new · authentication
List available countries
/api/v1/billing/countriesList 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 | Description |
|---|---|---|
product_kind (query) | string, nullable | Product: proxy_datacenter or proxy_isp |
geo_source (query) | string, nullable | Geo source the country tags are scoped to: maxmind or ripe |
Request example Try it in the Dashboard
curl -X GET "https://papaproxy.net/account_new/api/v1/billing/countries"
import requests
resp = requests.get(
"https://papaproxy.net/account_new/api/v1/billing/countries",
timeout=30,
)
resp.raise_for_status()
print(resp.json())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
$ch = curl_init('https://papaproxy.net/account_new/api/v1/billing/countries');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));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
200Countries you can order. · 7 fieldsExample 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 Description geo_sourcerequiredstring Geo source the country list is scoped to (maxmind/ripe) product_kindrequiredstring Product kind the availability counts are computed for countriesarray of object Available countries, most stock first countries[].coderequiredstring ISO 3166-1 alpha-2 code (e.g. 'DE', 'GB') countries[].namerequiredstring Country name (e.g. 'Germany') countries[].tag_idrequiredinteger Numeric country ID (the ISO code is accepted everywhere this ID is). countries[].availablerequiredinteger IPs currently available in this country 422Validation error: a field is missing or has the wrong type or value.Example · JSON{ "detail": "Validation error" }429Too 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
/api/v1/billing/composite/pricing-infoVolume-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 example Try it in the Dashboard
curl -X GET "https://papaproxy.net/account_new/api/v1/billing/composite/pricing-info"
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())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
$ch = curl_init('https://papaproxy.net/account_new/api/v1/billing/composite/pricing-info');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));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
200Pricing terms for display: volume-discount tiers, term discounts, order minimums, included concurrent connections, and add-on prices. · 22 fieldsExample 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 Description volume_tiersrequiredarray of object Volume-discount tiers, ascending by min_ips.volume_tiers[].min_ipsrequiredinteger Total-IP threshold at which this tier appliesMinimum: 0 volume_tiers[].price_millicents_per_iprequiredinteger Per-IP price in millicents (1/1000 of a cent)Minimum: 0 volume_tiers[].labelstring, nullable Human-readable tier label. period_discountsrequiredobject Period-months (string key) -> whole-percentage discount min_ips_per_segmentrequiredinteger Minimum IP count for a single segment to be acceptedMinimum: 1 min_ips_per_orderrequiredinteger Minimum total IP count (summed across segments) for an orderMinimum: 1 conn_cap_tiersarray of object Included concurrent connections by IP count. conn_cap_tiers[].min_ipsrequiredinteger Inclusive lower bound of this bucket's IP rangeMinimum: 0 conn_cap_tiers[].max_ipsinteger, nullable Exclusive upper bound; null = no upper bound (last bucket) conn_cap_tiers[].flat_capinteger, nullable Flat connection cap for this bucket conn_cap_tiers[].multiplierinteger, nullable Per-IP connection multiplier for this bucket whitelist_free_quotainteger Whitelist IPs included free with each service.Default: 0extra_whitelist_ip_price_centsinteger Monthly price per whitelist IP beyond the free allowance, in cents.Default: 0free_refresh_daysinteger Interval, in days, between free scheduled IP list refreshes.Default: 0trialobject Paid 48-hour trial settings: availability, length in days, and price coefficient. trial.enabledboolean Default: truetrial.period_daysinteger Term length in days.Minimum: 1Default: 2trial.tariff_coefficientnumber Default: 2trial.registration_window_enabledboolean Default: falsetrial.registration_window_daysinteger Minimum: 1Default: 30trial.max_per_monthinteger Minimum: 0Default: 3429Too 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
/api/v1/billing/composite/auto-planSpread 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
| Field | Type | Description |
|---|---|---|
total_ip required | integer | Total number of IPs to distribute. At least 100.Minimum: 1Maximum: 1000000 |
product_kind | string | Product: proxy_datacenter or proxy_isp. Dedicated proxies are priced and ordered through /api/v1/billing/dedicated/*.Default: "proxy_datacenter" |
geo_source | string | Geo source for country lookupsOne of: maxmind, ripeDefault: "maxmind" |
excluded_countries | array of integer or string | Countries to leave out, as ISO 3166-1 alpha-2 codes. |
Request example Try it in the Dashboard
curl -X POST "https://papaproxy.net/account_new/api/v1/billing/composite/auto-plan" \
-H "Content-Type: application/json" \
-d '{"total_ip": 300}'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())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
$ch = curl_init('https://papaproxy.net/account_new/api/v1/billing/composite/auto-plan');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => '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));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
200Proposed distribution of IPs by country. · 8 fieldsExample 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 Description snapshot_versionrequiredstring (uuid) Version of the availability data used for this response. segmentsarray of object Proposed per-country distribution, summing to total_ip segments[].countryrequiredinteger Numeric country ID. The ISO code is in country_code.segments[].country_codestring, nullable ISO 3166-1 alpha-2 country code (e.g. "DE"). segments[].countrequiredinteger Number of IPs allocated to this countryMinimum: 1 total_iprequiredinteger Total IP count across all segments (echoes the request) countries_usedrequiredinteger Number of distinct countries in the plan subnet_diversityinteger, nullable Number of distinct /24 subnets in the proposed allocation. Available for ISP proxies only; null for other products. 422Validation error, ortotal_ipis below 100.Example · JSON{ "detail": "Validation error" }429Too 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
/api/v1/billing/composite/quotePrice 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
errorsand left out ofsegmentsand the total. Always checkerrorsbefore you order. - An invalid
promo_codereturns422(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
| Field | Type | Description |
|---|---|---|
segments required | array of object | One or more segments forming the mixMin items: 1 |
segments[].country required | integer or string | ISO 3166-1 alpha-2 country code ("DE", "GB"). A numeric country ID from GET /api/v1/billing/countries is also accepted. |
segments[].count required | integer | Number of IPs in this country. At least 10; the order total must be at least 100.Minimum: 1Maximum: 100000 |
product_kind | string | Product: proxy_datacenter or proxy_isp. Dedicated proxies are priced and ordered through /api/v1/billing/dedicated/*.Default: "proxy_datacenter" |
period_months | integer | Term length in months (1, 3, 6, or 12). Longer terms get a discount. Default: 1. |
geo_source | string | Geo source for country lookups: maxmind (geolocation) or ripe (RIR allocation). Default maxmind.One of: maxmind, ripe |
promo_code | string, nullable | Optional promo code, applied after volume and term discounts.Max length: 64 |
trial | boolean | Price the paid 48-hour trial instead of a full term.Default: false |
extra_whitelist_count | integer | Paid whitelist IPs beyond the free allowance.Minimum: 0Maximum: 10000Default: 0 |
udp_unlock | boolean | Include the UDP add-on (a percentage of the base price).Default: false |
extra_connections_count | integer | Paid concurrent connections beyond those included, priced into the quote. For an existing service, buy the extra_connections add-on instead.Minimum: 0Maximum: 10000Default: 0 |
Request example Try it in the Dashboard
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}'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())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
$ch = curl_init('https://papaproxy.net/account_new/api/v1/billing/composite/quote');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => '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));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
200Price quote. Checkerrorsfor segments that were left out. · 42 fieldsExample 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 Description snapshot_versionrequiredstring (uuid) Version of the availability data used for this quote. built_atstring (date-time), nullable When the availability data was last updated (UTC). bindingboolean Always false — quotation is non-bindingDefault: falsesegmentsarray of object Per-segment pricing breakdown segments[].countryrequiredinteger Numeric country ID. The ISO code is in country_code.segments[].country_codestring, nullable ISO 3166-1 alpha-2 country code (e.g. "DE"). segments[].countrequiredinteger segments[].unit_base_centsrequiredinteger Volume-tier base unit price in integer cents per IP segments[].type_multiplierrequirednumber Product multiplier applied to the base rate. segments[].geo_multiplierrequirednumber Country multiplier (default 1.0). segments[].segment_price_centsrequiredinteger Segment price: count × base rate × product multiplier × country multiplier, rounded up to whole cents. segments[].availablerequiredboolean Whether enough IPs are available for this segment. errorsarray of object Errors for unavailable/invalid segments errors[].countryrequiredinteger Numeric country ID of the segment that was left out. errors[].messagerequiredstring total_ipinteger Total IP count across all valid segmentsDefault: 0total_price_centsinteger Discounted total charged for the full period in integer cents. Equals monthly_price_cents x period_months x (1 - discount).Default: 0unit_base_centsinteger Volume-tier base unit price applied (cents per IP)Default: 0period_monthsinteger Billing period length in months (1/3/6/12)Default: 1discount_pctinteger Term discount as a whole percentage. The volume discount is reported in volume_discount_pct.Default:0volume_discount_pctinteger Volume discount as a whole percentage, for display. Term discount is reported separately in discount_pct; both are already included intotal_price_cents.Default:0monthly_price_centsinteger Undiscounted monthly total (sum of segment prices) in integer centsDefault: 0retail_total_price_centsinteger, nullable Total before the partner discount, in cents. Null unless a partner discount applies. retail_monthly_price_centsinteger, nullable Monthly price before the partner discount, in cents. Null unless a partner discount applies. partner_discount_percentinteger, nullable Partner discount percentage, when it applies. partner_discount_centsinteger, nullable Partner discount amount in cents, when it applies. promo_code_appliedstring, nullable Promo code applied to this quote, if any. promo_discount_centsinteger Absolute promo discount in integer cents applied to total_price_cents.Minimum: 0Default: 0total_before_promo_centsinteger, nullable Pre-promo total in integer cents when a promo was applied; null when no promo. trialboolean True when the quote is for the paid trial.Default: falsetrial_period_daysinteger, nullable Trial length in days; null for regular quotes. trial_coefficientnumber, nullable Price coefficient of the trial; null for regular quotes. addon_monthly_centsinteger Monthly cents from pre-purchase add-ons (whitelist + UDP + connections).Minimum: 0Default: 0addon_breakdownarray of object Per-addon monthly line-items. Sum of monthly_cents equals addon_monthly_cents. addon_breakdown[].addon_typerequiredstring Add-on type.One of: extra_whitelist_ip,udp_unlock,extra_connectionsaddon_breakdown[].quantityrequiredinteger Number of units (IPs or connections; 1 for UDP).Minimum: 0 addon_breakdown[].unit_price_centsrequiredinteger Per-unit monthly centsMinimum: 0 addon_breakdown[].monthly_centsrequiredinteger Monthly cost: quantity × unit price (UDP is a percentage of the base price).Minimum: 0 trial_price_centsinteger, nullable Trial price in cents (before promo); null for regular quotes. extra_whitelist_countinteger Paid whitelist slots included in this quote.Minimum: 0Default: 0extra_connections_countinteger Pre-purchase extra concurrent connections included in this quote.Minimum: 0Default: 0udp_unlockboolean Whether UDP unlock was included in this quote.Default: false422Validation error, fewer than 100 IPs in total, or an invalid promo code.Example · JSON{ "detail": "Invalid promo code." }429Too 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
/api/v1/customer/catalogcatalog:readThe product catalog with your prices.
Parameters
| Field | Type | Description |
|---|---|---|
offset (query) | integer | Number of items to skip.Minimum: 0Default: 0 |
limit (query) | integer | Page size.Minimum: 1Maximum: 100Default: 50 |
Request example Try it in the Dashboard
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/catalog" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY"
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())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
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/catalog');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));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
200Paginated product list. · 27 fieldsExample 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 Description productsrequiredarray of object products[].idrequiredstring (uuid) Unique ID. products[].kindrequiredstring Product type, for example proxy_rotating.products[].billing_modelrequiredstring products[].namerequiredstring products[].descriptionrequiredstring, nullable products[].paramsrequiredobject, nullable Product parameters; they vary by product type (for example IP count, locations, term, request allowance, or session settings). products[].params.ip_countinteger, nullable Number of IPs. products[].params.geoarray of string, nullable products[].params.protocolstring, nullable products[].params.rate_limit_rpsinteger, nullable products[].params.stickyboolean, nullable products[].params.data_limit_bytesinteger, nullable Data allowance in bytes, for products with a data allowance. products[].params.traffic_gbnumber, nullable Data allowance in GB, for products with a data allowance. products[].params.pool_classstring, nullable IP pool type of the product.One of: datacenter,ispproducts[].params.devicesinteger, nullable products[].params.period_daysinteger, nullable Term length in days. products[].params.request_package_sizeinteger, nullable Prepaid request count for rotating proxies (for example 2,500,000). products[].price_centsinteger, nullable Resolved retail price in cents; null if none products[].price_unitstring, nullable products[].price_book_idstring (uuid), nullable ID of the price list the price comes from. products[].is_activerequiredboolean Whether it's active. products[].featuresarray of string Product features. totalrequiredinteger Total number of items across all pages. offsetinteger Number of items skipped.Default: 0limitinteger Page size.Default: 50price_book_kindstring One of: retail,wholesaleDefault:"retail"401The API key is missing, malformed, revoked, or expired, or the account is inactive.Example · JSON{ "detail": "Invalid or revoked API key." }403The API key lacks scopecatalog:read.Example · JSON{ "detail": "API key is missing required scope: catalog:read" }422Validation error: a field is missing or has the wrong type or value.Example · JSON{ "detail": "Validation error" }429Too 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
/api/v1/customer/catalog/{product_id}catalog:readProduct details with your price.
Parameters
| Field | Type | Description |
|---|---|---|
product_id (path) required | string (uuid) | ID of the product. |
Request example Try it in the Dashboard
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/catalog/3fa85f64-5717-4562-b3fc-2c963f66afa6" \ -H "Authorization: Bearer $PAPAPROXY_API_KEY"
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())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
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/catalog/3fa85f64-5717-4562-b3fc-2c963f66afa6');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));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
200A catalog product. · 22 fieldsExample 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 Description idrequiredstring (uuid) Unique ID. kindrequiredstring Product type, for example proxy_rotating.billing_modelrequiredstring namerequiredstring descriptionrequiredstring, nullable paramsrequiredobject, nullable Product parameters; they vary by product type (for example IP count, locations, term, request allowance, or session settings). params.ip_countinteger, nullable Number of IPs. params.geoarray of string, nullable params.protocolstring, nullable params.rate_limit_rpsinteger, nullable params.stickyboolean, nullable params.data_limit_bytesinteger, nullable Data allowance in bytes, for products with a data allowance. params.traffic_gbnumber, nullable Data allowance in GB, for products with a data allowance. params.pool_classstring, nullable IP pool type of the product.One of: datacenter,ispparams.devicesinteger, nullable params.period_daysinteger, nullable Term length in days. params.request_package_sizeinteger, nullable Prepaid request count for rotating proxies (for example 2,500,000). price_centsinteger, nullable Resolved retail price in cents; null if none price_unitstring, nullable price_book_idstring (uuid), nullable ID of the price list the price comes from. is_activerequiredboolean Whether it's active. featuresarray of string Product features. 401The API key is missing, malformed, revoked, or expired, or the account is inactive.Example · JSON{ "detail": "Invalid or revoked API key." }403The API key lacks scopecatalog:read.Example · JSON{ "detail": "API key is missing required scope: catalog:read" }404Product not found, or it doesn't belong to your account. — error format422Validation error: a field is missing or has the wrong type or value.Example · JSON{ "detail": "Validation error" }429Too 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" }
Last updated: November 9, 2026. Generated from the OpenAPI 3.1 specification (API version 0.1.0). Markdown version for AI agents.