PuppyIP
Skip to documentation

PUPPYIP / DEVELOPER DOCS

Customer API Documentation

The PuppyIP Customer API uses HMAC-SHA256 signing. Sandbox and production use the same paths, headers, signing rules and response envelope. After integration testing, you only need to replace the Base URL, App ID and API Secret.

Getting started

  1. 1. The production My API page links to sandbox integration testing. Sign in to the customer portal in the test environment, open My API, and create an App ID with the prefix sbx_app_ . Save the API Secret immediately.
  2. 2. Store PUPPYIP_API_BASE_URL, PUPPYIP_API_APP_ID and PUPPYIP_API_SECRET in server-side environment variables, not frontend pages.
  3. 3. First call GET /api/v1/account to verify signing, then call GET /api/v1/offers to retrieve available products.
  4. 4. When creating an order or renewal, generate one Idempotency-Key for each business operation. Timeout or network retries of that same operation must reuse the original key and exactly the same raw body. Change the key only for a new business operation.
  5. 5. Successful order creation means the order has been accepted. Save the returned order_no and check order details at the interval specified by poll_after_seconds until the order is completed or refunded.
  6. 6. Set a stable User-Agent for server-side requests, such as PuppyIP-Integration/1.0, to avoid security policies misidentifying default script identifiers.
  7. 7. After sandbox testing, return to production and create an App ID with the prefix live_app_ . For launch, replace only the production environment variables. After copying the examples, only the environment variables need changing; do not rewrite signing functions, headers or endpoint paths.
The sandbox produces only test balances, test orders and test IPs. Production charges your real wallet balance. The API has no top-up or external-payment endpoint, so keep sufficient funds in your PuppyIP account.

Storefront integration

Connect your website to PuppyIP to browse products, purchase, deliver and renew proxies. Your application manages its own customers, retail prices, payments, ledger and support.

Map customers to their orders

Your API key represents your PuppyIP account. The account, wallet, orders and proxies endpoints return that account’s data. Store a mapping from your customer to your local order, order_no and proxy_id on your server. Check ownership before displaying or managing a resource. Never expose your entire API account’s orders or proxy list to each customer.

The note field is an order annotation, not an access control or search filter. Current responses do not return it. Keep the API Secret and connection credentials out of public pages and logs.

  1. Display products

    GET /api/v1/offers

    Use /offers/available for available products, or /offers for the product list. Read every page and update products by offer_code. Request include=cidr_stock,line_group for per-range stock and Standard/Recommended categories.

  2. Check price and stock

    GET /api/v1/offers

    Request a quote for the selected quantity and duration. Manage your retail price separately. For a selected CIDR, check both cidr_stock and max_quantity: 0 means unavailable and null means unknown. Queries do not lock prices or reserve stock.

  3. Payments and balance

    GET /api/v1/wallet

    Customers pay your website; API purchases and renewals debit your PuppyIP wallet. Reconcile the two ledgers separately. The API does not collect payment from your customers or add funds for you.

  4. Create an order

    POST /api/v1/orders

    Save your local order, Idempotency-Key and exact JSON bytes before submitting. Retry a timeout with the same key and body but a fresh signature. Save order_no after HTTP 201; acceptance does not mean delivery is complete.

  5. Poll at poll_after_seconds and respect Retry-After on HTTP 429. Deliver only connections allowed by delivery.available. With partial delivery enabled, also check complete and remaining_count.

  6. Save proxy_id and retrieve its latest host, port, credentials and expiry to update your records. When webhook delivery is enabled, verify each notification and query current state; otherwise keep polling. The API does not update connection settings in your customers’ software.

  7. Renew and reconcile

    POST /api/v1/proxies/renew

    Select proxy_ids belonging to your local customer. Check successful, failed and unresolved items separately. Do not create another renewal for unresolved items. Refund fields describe the PuppyIP transaction; they do not mean you have refunded your own customer.

API v1 does not register sub-customers, collect their payments, add funds, accept refund requests, replace IPs or supply trial IPs. Manage your customer workflows in your own application. Credential updates require separate permission and an environment where the operation is enabled.

Switching environments

Store connection details in environment variables. For testing, use PUPPYIP_API_BASE_URL=https://sandbox.puppyip.com and an App ID beginning with sbx_app_ ; for production, switch to PUPPYIP_API_BASE_URL=https://puppyip.com and an App ID beginning with live_app_ . Headers, signing functions and endpoint paths do not change between testing and production.

PUPPYIP_API_BASE_URL=https://sandbox.puppyip.com
PUPPYIP_API_APP_ID=sbx_app_EXAMPLE_SANDBOX_APP_ID
PUPPYIP_API_SECRET=replace-with-your-secret
Do not hard-code sandbox offer_code values. During integration, first call GET /api/v1/offers to obtain available products, then pass a returned offer_code to POST /api/v1/orders. Before launch, call GET /api/v1/offers again and use the offer_code returned by production.
offer_code, order_no, proxy_id and page_token are public, opaque PuppyIP identifiers. Store and return them unchanged; do not infer business meaning from their contents. GET /api/v1/offers provides a snapshot at query time, not a reservation. POST /api/v1/orders checks price and availability again. Integrations should handle outcomes using stable error codes or order statuses.
An API key is a credential only for the PuppyIP Customer API. Sandbox and production keys are not interchangeable. Customer requests and documentation use only PuppyIP-issued App IDs and API Secrets. Orders and renewals use the account wallet only; the API has no separate top-up or external-payment endpoint.

Available endpoints

Request headers

Header Description Example
X-API-AppId The App ID generated on the account page. live_app_EXAMPLE_LIVE_APP_ID
X-API-Timestamp An absolute timestamp including a timezone. Clock skew of up to 5 minutes is allowed. 2026-06-28T12:00:00+00:00
X-API-Nonce A unique random value for each request. Reuse is rejected. nonce-empty-body
X-API-Signature The lowercase hexadecimal HMAC-SHA256 of the canonical string, calculated using the API Secret. e3c27a2055ad38f16861790294802b9fac81e02e8d72fbeaa87299598785832d
Idempotency-Key Required for order creation, renewal, credential changes, and webhook configuration or deletion where available. After trimming leading and trailing whitespace, use 8–160 characters containing only ASCII letters, digits, . _ : -. Missing or invalid keys return HTTP 400 / idempotency_key_required. Reuse the key and identical raw body for retries. Order and renewal replay is account-scoped, including after App ID rotation; webhook replay is scoped to the API key that owns the subscription. Orders, renewals and webhook settings replay the saved response. Credential changes return the existing operation’s current status. A different body with the same key returns a conflict. order-demo-001
User-Agent Use a stable server-side client identifier to avoid security policies misidentifying default script identifiers. PuppyIP-Integration/1.0
X-PuppyIP-Sandbox-Scenario Sandbox orders accept success, delayed, partial, rejected, final_partial or indeterminate. Sandbox renewals accept completed, partial, failed or unresolved. Omit to keep the default simulation. Production orders reject a nonempty value; production renewals ignore this header. partial

Canonical String

Before signing, split the request into 7 lines and join them with the newline character \n :

METHOD
host
path
canonical_query
timestamp
nonce
sha256(body)

METHOD must be uppercase, for example GET or POST.

host is the lowercase hostname from the Base URL, without the scheme.

path must start with a slash, for example /api/v1/account.

canonical_query URL-decodes each query key and value, then applies rawurlencode and sorts by key and value in ascending order. Repeated keys are not merged. Leave this line empty when there is no query.

body is hashed with SHA-256 using the raw request body. A GET request without a body uses the SHA-256 of an empty string.

Fixed GET /api/v1/account example

GET
puppyip.com
/api/v1/account

2026-06-28T12:00:00+00:00
nonce-empty-body
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

The fixed example above uses the documentation test Secret puppyip-docs-test-secret and produces this signature: e3c27a2055ad38f16861790294802b9fac81e02e8d72fbeaa87299598785832d.

Code examples

These examples use environment variables. Do not put your API Secret in frontend pages, public repositories or logs.

curl

curl only sends the request. First use the PHP or Node.js signing function below to generate the current TIMESTAMP, NONCE and SIGNATURE for this request. Do not send requests using the fixed algorithm-check values above, and do not write your Secret into shell history.

curl "$PUPPYIP_API_BASE_URL/api/v1/account" \
  -H "X-API-AppId: $PUPPYIP_API_APP_ID" \
  -H "X-API-Timestamp: $TIMESTAMP" \
  -H "X-API-Nonce: $NONCE" \
  -H "X-API-Signature: $SIGNATURE" \
  -H "Accept: application/json" \
  -H "User-Agent: PuppyIP-Integration/1.0"

PHP

$baseUrl = rtrim(getenv('PUPPYIP_API_BASE_URL') ?: 'https://sandbox.puppyip.com', '/');
$appId = getenv('PUPPYIP_API_APP_ID') ?: '';
$secret = getenv('PUPPYIP_API_SECRET') ?: '';
$method = 'GET';
$target = $baseUrl.'/api/v1/account';
$parts = parse_url($target);
$path = $parts['path'] ?? '/';
$rawQuery = $parts['query'] ?? '';
$body = '';
$timestamp = gmdate('Y-m-d\TH:i:s\Z');
$nonce = 'nce_'.bin2hex(random_bytes(16));
$host = strtolower($parts['host'] ?? '');

function canonicalQuery(string $rawQuery): string
{
    if ($rawQuery === '') {
        return '';
    }

    $pairs = array_map(function (string $pair): array {
        [$key, $value] = array_pad(explode('=', $pair, 2), 2, '');

        return [rawurlencode(rawurldecode($key)), rawurlencode(rawurldecode($value))];
    }, explode('&', $rawQuery));

    usort($pairs, fn (array $left, array $right): int => $left[0] <=> $right[0] ?: $left[1] <=> $right[1]);

    return implode('&', array_map(fn (array $pair): string => $pair[0].'='.$pair[1], $pairs));
}

$canonical = implode("\n", [
    $method,
    $host,
    $path,
    canonicalQuery($rawQuery),
    $timestamp,
    $nonce,
    hash('sha256', $body),
]);

$signature = hash_hmac('sha256', $canonical, $secret);
$headers = [
    'X-API-AppId: '.$appId,
    'X-API-Timestamp: '.$timestamp,
    'X-API-Nonce: '.$nonce,
    'X-API-Signature: '.$signature,
    'Accept: application/json',
    'User-Agent: PuppyIP-Integration/1.0',
];

$handle = curl_init($target);
curl_setopt_array($handle, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => $headers,
]);
$response = curl_exec($handle);

if ($response === false) {
    throw new RuntimeException(curl_error($handle));
}

echo $response;

Node.js

import crypto from 'node:crypto';

const baseUrl = process.env.PUPPYIP_API_BASE_URL ?? 'https://sandbox.puppyip.com';
const appId = process.env.PUPPYIP_API_APP_ID ?? '';
const secret = process.env.PUPPYIP_API_SECRET ?? '';
const method = 'GET';
const url = new URL('/api/v1/account', baseUrl);
const body = '';
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
const nonce = `nce_${crypto.randomBytes(16).toString('hex')}`;

const rfc3986 = (value) => encodeURIComponent(value).replace(
  /[!'()*]/g,
  (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,
);
const compare = (left, right) => left < right ? -1 : (left > right ? 1 : 0);
const canonicalQuery = (rawQuery) => {
  if (rawQuery === '') return '';

  return rawQuery
    .split('&')
    .map((pair) => {
      const separator = pair.indexOf('=');
      const key = separator === -1 ? pair : pair.slice(0, separator);
      const value = separator === -1 ? '' : pair.slice(separator + 1);

      return [rfc3986(decodeURIComponent(key)), rfc3986(decodeURIComponent(value))];
    })
    .sort((left, right) => compare(left[0], right[0]) || compare(left[1], right[1]))
    .map(([key, value]) => `${key}=${value}`)
    .join('&');
};
const rawQuery = url.search.startsWith('?') ? url.search.slice(1) : url.search;

const canonical = [
  method,
  url.hostname.toLowerCase(),
  url.pathname,
  canonicalQuery(rawQuery),
  timestamp,
  nonce,
  crypto.createHash('sha256').update(body).digest('hex'),
].join('\n');

const signature = crypto
  .createHmac('sha256', secret)
  .update(canonical)
  .digest('hex');

const headers = {
  'X-API-AppId': appId,
  'X-API-Timestamp': timestamp,
  'X-API-Nonce': nonce,
  'X-API-Signature': signature,
  Accept: 'application/json',
  'User-Agent': 'PuppyIP-Integration/1.0',
};

const response = await fetch(url, { method, headers });
console.log(await response.json());

Response envelope

All endpoints return a consistent JSON envelope. On success, success=true and the business data is in data. On failure, code is the stable error code, and trace_id helps support investigate the request.

{
  "success": true,
  "code": "ok",
  "message": "OK",
  "data": {},
  "timestamp": "2026-06-28T12:00:00+00:00",
  "trace_id": "trc_EXAMPLE_TRACE_ID"
}
GET/api/v1/account

Get account

Check the account associated with your API key.

Required scopeaccount:read

Request parameters

No additional business parameters. Signed request headers are still required.

  • Every operation is scoped to the account owning the key. IP replacement and trial IPs are not available through this API.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/account

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.account_idstringPublic account identifier. Store it as an opaque string.
data.emailstringAccount email address.
data.email_verifiedbooleanWhether the account email is verified.
data.statusstringactive or inactive.
data.created_atstring|nullCreation time in ISO 8601 UTC, or null when unavailable.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "account_id": "acct_0123456789abcdef0123456789",
        "email": "[email protected]",
        "email_verified": true,
        "status": "active",
        "created_at": "2026-11-09T00:00:00+00:00"
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/wallet

Get wallet balance

Check the balance and currency used for API purchases.

Required scopewallet:read

Request parameters

No additional business parameters. Signed request headers are still required.

  • Use the returned currency and integer amounts for balance checks. This API does not provide an add-funds operation; sandbox funds have no cash value.
  • Calculate money with integer minor units or exact decimal arithmetic, not binary floating point. Decimal amount fields are strings with two places.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/wallet

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.currencystringCurrency used by this response’s amounts.
data.balance_centsintegerWallet balance in the currency’s minor units; 100 cents = 1 unit.
data.balancestringThe same wallet balance as a decimal string with two places.
data.frozen_centsintegerFrozen wallet amount in minor units.
data.cash_valuebooleantrue for a production wallet; false for simulated sandbox funds.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "currency": "CNY",
        "balance_cents": 10000,
        "balance": "100.00",
        "frozen_cents": 0,
        "cash_value": true
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/offers

Browse products

Find available products, prices, durations and IP ranges before ordering.

Required scopeoffers:read

Request parameters

FieldLocationRequirementDescription
page_sizeinteger query Optional Items per page: 1–200; default 200 when using pagination.
page_tokenstring query Optional The previous response’s next_page_token, unchanged. Omit for the first page. Maximum 4,096 characters.
country_codestring query Optional Match a returned country_code, such as US. Use either country_code or country.
countrystring query Optional Alias for country_code. If both are supplied, both conditions must match.
regionstring query Optional Match a returned region value. Use either region or city.
citystring query Optional Alias for region. If both are supplied, both conditions must match.
linestring query Optional Search the product display name for this text.
ip_typestring query Optional Match the returned IP type. Current codes: 1 = broadcast, 2 = native residential.
quantityinteger query Optional Quantity used to calculate the unit price; default 1, limited to 1–2000. This is not a minimum-stock filter.
duration_daysinteger query Optional Duration used for pricing; default 30 days, limited to 1–1,095. Only products supporting that duration are returned.
line_groupstring query Optional ordinary or recommended. Filters the list before pagination; does not add a response field by itself.
selectionstring query Optional Set to highest_stock to return the same available product list as /offers/available. Omit to use the default /offers list.
location_idstring query Optional A location_id from the location directory: loc_ followed by 26 lowercase hexadecimal characters. A well-formed unknown ID returns an empty list.
tagstring query Optional ordinary or recommended; combined with all other filters.
includestring query Optional Optional extensions: cidr_stock, line_group and catalog; combine with commas. Omit to keep the default response.
  • Quotes use your account pricing, requested quantity and duration; they do not reserve stock. Prices and stock can change before purchase. Do not add stock counts from different products together. A stock value of null means unknown, not zero or unlimited.
  • Calculate money with integer minor units or exact decimal arithmetic, not binary floating point. Decimal amount fields are strings with two places.
  • Pass next_page_token back unchanged and keep the same filters and delivery mode. Tokens expire after 3 days. Continue while has_more is true, even after an empty page. Pages are not a frozen snapshot.
  • The sandbox includes Standard, Recommended and CIDR test products, with per-range stock values of 3, 0 and null. null means unknown, not zero or total stock. Quantity limits apply per request; global stock depletion is not simulated. Returned connections are for testing and do not represent real exits in the selected range.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/offers?page_size=200

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.currencystringCurrency used by this response’s amounts.
data.payment_modestringwallet. API purchases use the account wallet.
data.offersobject[]Products on this page. An empty array does not end pagination if has_more is true.
data.offers[].offer_codestringPublic product identifier to submit when ordering. Do not parse it.
data.offers[].namestringCustomer-facing product name.
data.offers[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.offers[].country_namestringCountry display name.
data.offers[].regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.
data.offers[].line_namestringCustomer-facing location or line label; not a stable identifier.
data.offers[].ip_typestring|nullCurrent codes: 1 = broadcast, 2 = native residential. Historical text values or null may also appear; preserve the returned value.
data.offers[].protocolstring|nullCompatibility field; current products commonly return 0. This value does not select HTTP versus SOCKS5. May be null.
data.offers[].unit_price_centsintegerPer-IP price in minor units for the requested quantity and duration.
data.offers[].unit_pricestringThe same per-IP price as a decimal string with two places.
data.offers[].base_duration_daysintegerDuration covered by the quoted unit price.
data.offers[].duration_daysinteger[]Supported purchase durations, in days.
data.offers[].max_quantityintegerCurrent total purchase limit for this product, after availability constraints. It is not the stock of every IP range.
data.offers[].cidrsstring[]Selectable IP ranges. An empty array means no specific range is offered.
data.offers[].cidr_stockOpt-in fieldobject<string, integer|null>Only with include=cidr_stock. Maps each CIDR to its available count or null if unknown. Empty maps are {}. Zero means no available stock.
data.offers[].stock_updated_atOpt-in fieldstring|nullOnly with include=cidr_stock. Inventory snapshot time in ISO 8601 UTC, or null. Sandbox returns null.
data.offers[].line_groupOpt-in fieldstringOnly with include=line_group. ordinary or recommended.
data.offers[].location_idOpt-in fieldstring|nullPublic location identifier, or null if unknown. Only with include=catalog.
data.offers[].tagOpt-in fieldstringordinary or recommended. Only with include=catalog.
data.page_sizeintegerPage-size limit applied to this response, not the number of returned records.
data.has_morebooleanWhether another page may be requested.
data.next_page_tokenstring|nullOpaque token for the next page; null when there is no next page.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "currency": "CNY",
        "payment_mode": "wallet",
        "offers": [
            {
                "offer_code": "off_0123456789abcdef0123456789",
                "name": "United States - Los Angeles",
                "country_code": "US",
                "country_name": "United States",
                "region": "Los Angeles",
                "line_name": "United States - Los Angeles",
                "ip_type": "2",
                "protocol": "0",
                "unit_price_cents": 2000,
                "unit_price": "20.00",
                "base_duration_days": 30,
                "duration_days": [
                    30,
                    60,
                    90
                ],
                "max_quantity": 12,
                "cidrs": [
                    "198.51.100.0/24"
                ]
            }
        ],
        "page_size": 200,
        "has_more": false,
        "next_page_token": null
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}

Product types, protocols and stock

ip_type is usually a string: "1" means Broadcast IP; "2" means Native Residential IP. A missing value may be null and must be treated as unknown. Historical or sandbox data may also contain text values such as broadcast, residential, native_residential, origin_residential or static_residential. Use the returned value when filtering. Preserve unknown values without assigning them to either category, and use meaningful product names instead of “Type 1 / Type 2”.

The Standard/Recommended route classification is separate from ip_type (Broadcast/Native Residential). Request include=line_group to add line_group to each offer: ordinary means Standard; recommended means Recommended. The classification matches the website and belongs to the offer_code. Its cidrs belong to that offer; do not infer the classification from names, prices, purchase durations or CIDR strings.

Use line_group=ordinary for Standard offers or line_group=recommended for Recommended offers. Omit it or leave it empty to show both. Only these two lowercase values are accepted when nonempty; other values or arrays return HTTP 422 / validation_error. Filtering does not add response fields automatically; also request include=line_group if you need the classification in the response.

To request both classification and stock, use include=cidr_stock,line_group. Separate extensions with commas; their order does not change the included data. Existing requests keep their response fields; include=cidr_stock alone still adds only inventory and its observation time. Place orders using the returned offer_code and optional cidr; do not add line_group to the order payload.

Keep the same classification and other filters when paging, and repeat the include extensions you need. Page tokens are bound to the classification: using a Standard token for Recommended offers or an unfiltered catalog returns invalid_page_token. Restart from the first page when changing filters. Sign all query parameters using the existing signing rules.

Static proxies currently support both HTTP and SOCKS5 by default. protocol is retained for compatibility and is not used to select or restrict the connection protocol; clients do not need to choose a protocol based on "0". Connect using the delivered host, port, username and password, and select HTTP or SOCKS5 in your client. connection and socks5_uri are connection formats, not connectivity test results.

max_quantity is the current total purchase limit for this offer, not the quantity available in every CIDR or a stock reservation. Do not add max_quantity values from different products as regional stock. In the default /offers list, quantity is used for pricing without filtering by stock. Use /offers/available to find available products for a requested quantity.

cidrs lists selectable network ranges; inclusion does not guarantee availability. The cidr query parameter does not filter stock on GET /api/v1/offers or change max_quantity into a per-CIDR limit. For a specified CIDR, use the per-CIDR values below and stay within the offer total. Stock is checked again when an order is created.

GET /api/v1/offers?include=cidr_stock adds cidr_stock and stock_updated_at to each offer. These two fields are absent unless the cidr_stock extension is requested. Repeat the extension on subsequent pages and sign all query parameters as usual.

cidr_stock is an object keyed by CIDR. A positive integer is the current snapshot purchase limit for that range; 0 means unavailable; null means unknown. Do not treat null as zero, unlimited stock or the offer total, and do not display a definite quantity for an unknown range. Offers with no selectable CIDRs return {}. All ranges share the offer total and remain subject to max_quantity.

stock_updated_at is the inventory observation time in UTC ISO 8601 format, or null when unknown. Reading the API does not refresh live inventory or reserve resources. Availability may change between requests. Update your display from the latest response and handle the order creation and delivery outcomes. Sandbox stock is simulated and this timestamp is null.

This example filters Recommended offers and requests both classification and per-CIDR stock. The response shows only the relevant fields, using sample values and network ranges.

GET /api/v1/offers?line_group=recommended&include=cidr_stock,line_group&page_size=200

{
  "offer_code": "offer-code-from-response",
  "line_group": "recommended",
  "ip_type": "2",
  "protocol": "0",
  "max_quantity": 20,
  "cidrs": ["198.51.100.0/24", "203.0.113.0/24", "192.0.2.0/24"],
  "cidr_stock": {
    "198.51.100.0/24": 12,
    "203.0.113.0/24": 0,
    "192.0.2.0/24": null
  },
  "stock_updated_at": "2026-11-09T08:00:00+00:00"
}
POST/api/v1/orders

Create an order

Buy one or more products using your wallet, then check the order for delivery.

Required scopeorders:write

Request parameters

FieldLocationRequirementDescription
Idempotency-Keystring header Required Required for writes. Use 8–160 characters from A–Z, a–z, 0–9, period, underscore, colon and hyphen. Keep the same key and exact request body when retrying.
X-PuppyIP-Sandbox-Scenariostring header Optional Sandbox orders only: success, delayed, partial, rejected, final_partial or indeterminate. Omit for immediate simulation. Production orders reject a nonempty value.
offer_codestring body Optional Required for the single-product format; maximum 80 characters. Use an offer_code from the product list. Do not combine with items.
quantityinteger body Optional Required for the single-product format: 1–2000. The combined quantity across all items must also stay within 2000.
cidrstring|null body Optional Optional IP range from the selected product’s cidrs; maximum 64 characters. Omit or use null for automatic allocation.
itemsobject[] body Optional Alternative to offer_code/quantity/cidr: 1–50 product lines. Do not combine the two formats. Every line uses the top-level duration_days.
items[].offer_codestring body Required Required in each items entry. Product offer_code, maximum 80 characters.
items[].quantityinteger body Required Required in each items entry: 1–2000. The total across all entries must not exceed 2000.
items[].cidrstring|null body Optional Optional IP range from the selected product’s cidrs; maximum 64 characters. Omit or use null for automatic allocation.
duration_daysinteger body Required Positive multiple of 30, at most 1,095 days, and supported by every selected product.
notestring|null body Optional Optional order note, up to 1,000 characters. Stored with the order but not returned by these API responses.
external_order_nostring|null body Optional Your own order reference, optional or null. Up to 80 characters, starting with a letter or digit; letters, digits, period, underscore, colon and hyphen only.
  • HTTP 201 confirms order creation, not completed delivery. Save order_no and follow poll_after_seconds. For one product, do not repeat a CIDR or mix automatic allocation with specified CIDRs in the same request.
  • Calculate money with integer minor units or exact decimal arithmetic, not binary floating point. Decimal amount fields are strings with two places.
  • After a timeout, retry with the original Idempotency-Key and identical body bytes, using a fresh timestamp and nonce. Never change the key merely because the result is still pending.
  • external_order_no is unique within your account and environment. Reusing it with the identical raw request body returns the original creation result without another charge; a different body returns 409 external_order_conflict. Reads add it only with include=external_order_no.
  • Sandbox final_partial requires at least 2 IPs: some are delivered and confirmed failures are refunded, ending as delivered with payment_status=partially_refunded. indeterminate remains procurement_blocked without delivery, automatic refund or repeated execution. The existing partial scenario still delivers some IPs first and completes later.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

POST /api/v1/orders
Content-Type: application/json
Idempotency-Key: example-create-order-001

{
    "offer_code": "off_0123456789abcdef0123456789",
    "quantity": 1,
    "duration_days": 30
}

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.external_order_noOpt-in fieldstring|nullYour order reference. Creation includes it only when supplied; reads include it only with include=external_order_no, using null when absent.
data.order_nostringPublic order reference used to retrieve order details.
data.statusstringCommon values: pending_payment, paid, procuring, partially_delivered, procurement_blocked, delivered, refunded or cancelled. Check delivery and refund fields as well.
data.payment_statusstringPayment state, such as unpaid, paid, partially_refunded, refunded, failed or cancelled. Do not infer it from HTTP success.
data.payment_methodstringRecorded payment method, such as wallet. Orders placed on the website can use other payment methods.
data.currencystringCurrency of the order’s business amounts. A refund may have a different currency; read refund.currency separately.
data.subtotal_centsintegerOrder subtotal in minor units.
data.total_centsintegerOrder total in minor units.
data.totalstringThe total as a decimal string with two places.
data.created_atstring|nullCreation time in ISO 8601 UTC, or null when unavailable.
data.itemsobject[]Customer order lines.
data.items[].namestringCustomer-facing product name.
data.items[].quantityintegerNumber of IPs ordered on this line.
data.items[].duration_daysintegerPurchased duration in days.
data.items[].offer_codestring|nullPublic product identifier, or null when no product link is available.
data.items[].unit_price_centsintegerRecorded per-IP price in minor units.
data.items[].total_price_centsintegerRecorded line total in minor units.
data.deliveryobjectDelivery counts and access state.
data.delivery.expected_countintegerNumber of IPs originally ordered.
data.delivery.delivered_countintegerNumber of connections currently recorded for the order. Check available before using them.
data.delivery.completebooleanWhether the order’s delivery is complete. A completed partial refund can leave fewer delivered IPs than originally ordered.
data.delivery.availablebooleanWhether connections can be read in the current delivery mode.
data.poll_after_secondsinteger|nullSuggested delay before checking the order again; null when no further delivery polling is suggested.
data.refundobject|nullRefund progress, or null when no refund information is available. A successful refund can cover only part of an order.
data.refund.statusstring|nullsucceeded, pending, failed or null.
data.refund.destinationstring|nullwallet when the recorded destination is the site wallet; otherwise null. Null does not confirm an external refund.
data.refund.amount_centsinteger|nullRecorded refund amount in minor units, or null when a reliable single amount is unavailable. Null is not zero.
data.refund.currencystring|nullCurrency of the recorded refund amount, or null. Do not substitute the order currency.
data.refund.refunded_atstring|nullRecorded refund time in ISO 8601 UTC, or null.
data.proxiesobject[]Available connection records. The order response uses an empty array until its delivery mode permits access.
data.proxies[].proxy_idstringPublic identity of this proxy resource. Save this ID to query or renew it; connection details may change.
data.proxies[].statusstringCurrent resource status, such as active or expired. Use expires_at as well when checking validity.
data.proxies[].hoststringConnection host or IP address. Use the current returned value.
data.proxies[].portintegerConnection port.
data.proxies[].protocolstring|nullCompatibility field; current products commonly return 0. This value does not select HTTP versus SOCKS5. May be null.
data.proxies[].usernamestring|nullConnection username, or null when unavailable. Treat as sensitive.
data.proxies[].passwordstringConnection password. Treat as sensitive.
data.proxies[].connectionstringConnection text in host:port:username:password format. Treat as sensitive.
data.proxies[].socks5_uristringSOCKS5 connection URI with encoded credentials. Treat as sensitive.
data.proxies[].ip_typestring|nullCurrent codes: 1 = broadcast, 2 = native residential. Historical text values or null may also appear; preserve the returned value.
data.proxies[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.proxies[].regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.
data.proxies[].line_namestring|nullCustomer-facing location or line label; not a stable identifier.
data.proxies[].starts_atstring|nullStart time in ISO 8601 UTC, or null.
data.proxies[].expires_atstring|nullExpiry time in ISO 8601 UTC, or null.

Response example HTTP 201

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "order_no": "PSO20261109000000EXAMPLE",
        "status": "procuring",
        "payment_status": "paid",
        "payment_method": "wallet",
        "currency": "CNY",
        "subtotal_cents": 2000,
        "total_cents": 2000,
        "total": "20.00",
        "created_at": "2026-11-09T00:00:00+00:00",
        "items": [
            {
                "name": "United States - Los Angeles",
                "quantity": 1,
                "duration_days": 30,
                "offer_code": "off_0123456789abcdef0123456789",
                "unit_price_cents": 2000,
                "total_price_cents": 2000
            }
        ],
        "delivery": {
            "expected_count": 1,
            "delivered_count": 0,
            "complete": false,
            "available": false
        },
        "poll_after_seconds": 1,
        "refund": null,
        "proxies": []
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}

Create a multi-region order

POST /api/v1/orders
Idempotency-Key: order-demo-001

{
  "duration_days": 30,
  "items": [
    {"offer_code": "sandbox_us_static", "quantity": 2},
    {"offer_code": "sandbox_jp_static", "quantity": 1}
  ]
}

items allows up to 50 lines, with a total order quantity no greater than 2000. For one product, do not submit the same CIDR twice or mix random allocation with a specified CIDR. The legacy single-region format offer_code + quantity + duration_days remains supported but cannot be combined with items. duration_days must be a positive multiple of 30 and supported by the selected products.

Order notes (optional)

Downstream integrators can use POST /api/v1/orders with a top-level JSON note to distinguish their own purchases from purchases for customers. note is an optional string of up to 1000 characters, trimmed when saved. Omitted, null, empty or whitespace-only values retain the original no-note behavior. Non-string or overlong notes return HTTP 422 with code=validation_error, without creating an order or charging the wallet.

POST /api/v1/orders
Idempotency-Key: order-note-demo-001

{
  "offer_code": "sandbox_us_static",
  "quantity": 2,
  "duration_days": 30,
  "note": "客户A / 业务单号12345"
}

Both the legacy single-region format and multi-region items format support the same top-level note, not a field inside items. The order note is applied to all IPs at their initial delivery for that order. After delivery, view, search, edit or clear it in the existing note field in the website's IP management page. Later delivery retries and synchronization do not restore or overwrite edited notes. Notes are private business labels for the account; they do not change prices, charges, stock or IP ownership. Historical orders are not backfilled.

Production and sandbox use the same note rules. Existing clients do not need a new parameter. Existing order-creation, order-query and IP-query response fields remain unchanged; no note response field is added. Sign the full raw JSON actually sent, including note. Do not trim note whitespace before signing a separate payload. Retries must keep the original body. Changing the note with the same Idempotency-Key returns 409 idempotency_key_conflict. Do not edit notes by resending a purchase request or use a new key to place another order for that purpose.

GET/api/v1/orders

List orders

Browse your account’s order history, including orders placed on the website.

Required scopeorders:read

Request parameters

FieldLocationRequirementDescription
page_sizeinteger query Optional Items per page: 1–100; default 50 when using pagination.
page_tokenstring query Optional The previous response’s next_page_token, unchanged. Omit for the first page. Maximum 4,096 characters.
X-PuppyIP-Delivery-Modestring header Optional Optional value partial on this GET request. Makes already available connections readable while other units are still pending.
order_nostring query Optional Exact PuppyIP order_no, up to 80 characters.
external_order_nostring query Optional Exact external_order_no, up to 80 characters. Filtering does not add the response field.
statusstring query Optional Exact public status returned by this endpoint, up to 40 characters. An unknown status returns an empty list.
created_fromstring query Optional Inclusive start time in ISO 8601 with an explicit timezone. Supports up to 6 fractional digits, four-digit years 0001–9999 and offsets up to ±14:00; compared in UTC.
created_tostring query Optional Inclusive end time, using the same format as the start; must not precede it.
includestring query Optional Use include=external_order_no to add your order reference, or null for records without one. Omit to keep existing fields.
  • The list returns summaries without connection credentials or item prices. Status follows the current delivery mode. Continue paging while has_more=true, even when a page is empty.
  • Partial delivery is optional on GET requests only. Without the header, the existing delivery behavior and response fields remain unchanged.
  • Pass next_page_token back unchanged and keep the same filters and delivery mode. Tokens expire after 3 days. Continue while has_more is true, even after an empty page. Pages are not a frozen snapshot.
  • Filters are combined. Repeat equivalent filters and delivery mode on later pages; changing or removing them returns invalid_page_token. Date ranges include both endpoints.
  • external_order_no is unique within your account and environment. Reusing it with the identical raw request body returns the original creation result without another charge; a different body returns 409 external_order_conflict. Reads add it only with include=external_order_no.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/orders?page_size=50

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.ordersobject[]Order summaries on this page; connection credentials are returned by order details.
data.orders[].external_order_noOpt-in fieldstring|nullYour order reference. Creation includes it only when supplied; reads include it only with include=external_order_no, using null when absent.
data.orders[].order_nostringPublic order reference used to retrieve order details.
data.orders[].statusstringCommon values: pending_payment, paid, procuring, partially_delivered, procurement_blocked, delivered, refunded or cancelled. Check delivery and refund fields as well.
data.orders[].payment_statusstringPayment state, such as unpaid, paid, partially_refunded, refunded, failed or cancelled. Do not infer it from HTTP success.
data.orders[].payment_methodstringRecorded payment method, such as wallet. Orders placed on the website can use other payment methods.
data.orders[].currencystringCurrency of the order’s business amounts. A refund may have a different currency; read refund.currency separately.
data.orders[].total_centsintegerOrder total in minor units.
data.orders[].totalstringThe total as a decimal string with two places.
data.orders[].created_atstring|nullCreation time in ISO 8601 UTC, or null when unavailable.
data.orders[].itemsobject[]Customer order lines.
data.orders[].items[].namestringCustomer-facing product name.
data.orders[].items[].quantityintegerNumber of IPs ordered on this line.
data.orders[].items[].duration_daysintegerPurchased duration in days.
data.orders[].items[].offer_codestring|nullPublic product identifier, or null when no product link is available.
data.orders[].deliveryobjectDelivery counts and access state.
data.orders[].delivery.expected_countintegerNumber of IPs originally ordered.
data.orders[].delivery.delivered_countintegerNumber of connections currently recorded for the order. Check available before using them.
data.orders[].delivery.completebooleanWhether the order’s delivery is complete. A completed partial refund can leave fewer delivered IPs than originally ordered.
data.orders[].delivery.availablebooleanWhether connections can be read in the current delivery mode.
data.orders[].delivery.remaining_countOpt-in fieldintegerOnly with X-PuppyIP-Delivery-Mode: partial. Units still awaiting an outcome after delivered and confirmed refunded units; zero for a final order.
data.orders[].poll_after_secondsinteger|nullSuggested delay before checking the order again; null when no further delivery polling is suggested.
data.orders[].refundobject|nullRefund progress, or null when no refund information is available. A successful refund can cover only part of an order.
data.orders[].refund.statusstring|nullsucceeded, pending, failed or null.
data.orders[].refund.destinationstring|nullwallet when the recorded destination is the site wallet; otherwise null. Null does not confirm an external refund.
data.orders[].refund.amount_centsinteger|nullRecorded refund amount in minor units, or null when a reliable single amount is unavailable. Null is not zero.
data.orders[].refund.currencystring|nullCurrency of the recorded refund amount, or null. Do not substitute the order currency.
data.orders[].refund.refunded_atstring|nullRecorded refund time in ISO 8601 UTC, or null.
data.page_sizeintegerPage-size limit applied to this response, not the number of returned records.
data.has_morebooleanWhether another page may be requested.
data.next_page_tokenstring|nullOpaque token for the next page; null when there is no next page.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "orders": [
            {
                "order_no": "PSO20261109000000EXAMPLE",
                "status": "delivered",
                "payment_status": "paid",
                "payment_method": "wallet",
                "currency": "CNY",
                "total_cents": 2000,
                "total": "20.00",
                "created_at": "2026-11-09T00:00:00+00:00",
                "items": [
                    {
                        "name": "United States - Los Angeles",
                        "quantity": 1,
                        "duration_days": 30,
                        "offer_code": "off_0123456789abcdef0123456789"
                    }
                ],
                "delivery": {
                    "expected_count": 1,
                    "delivered_count": 1,
                    "complete": true,
                    "available": true
                },
                "poll_after_seconds": null,
                "refund": null
            }
        ],
        "page_size": 50,
        "has_more": false,
        "next_page_token": null
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/orders/{order_no}

Get order details

Check payment, delivery and refund progress, and retrieve available connections.

Required scopeorders:read

Request parameters

FieldLocationRequirementDescription
order_nostring path Required The order_no returned when the order was created. Only your own orders are accessible.
X-PuppyIP-Delivery-Modestring header Optional Optional value partial on this GET request. Makes already available connections readable while other units are still pending.
includestring query Optional Use include=external_order_no to add your order reference, or null for records without one. Omit to keep existing fields.
  • Use delivery.available to decide whether connections are readable. Check refund.currency and refund.amount_cents independently of the order total; an unknown refund amount is not zero.
  • Keep connection passwords and complete connection strings in trusted server-side storage, out of public pages and logs. Times use ISO 8601 with a UTC offset; null means unavailable, not zero.
  • Partial delivery is optional on GET requests only. Without the header, the existing delivery behavior and response fields remain unchanged.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/orders/PSO20261109000000EXAMPLE

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.external_order_noOpt-in fieldstring|nullYour order reference. Creation includes it only when supplied; reads include it only with include=external_order_no, using null when absent.
data.order_nostringPublic order reference used to retrieve order details.
data.statusstringCommon values: pending_payment, paid, procuring, partially_delivered, procurement_blocked, delivered, refunded or cancelled. Check delivery and refund fields as well.
data.payment_statusstringPayment state, such as unpaid, paid, partially_refunded, refunded, failed or cancelled. Do not infer it from HTTP success.
data.payment_methodstringRecorded payment method, such as wallet. Orders placed on the website can use other payment methods.
data.currencystringCurrency of the order’s business amounts. A refund may have a different currency; read refund.currency separately.
data.subtotal_centsintegerOrder subtotal in minor units.
data.total_centsintegerOrder total in minor units.
data.totalstringThe total as a decimal string with two places.
data.created_atstring|nullCreation time in ISO 8601 UTC, or null when unavailable.
data.itemsobject[]Customer order lines.
data.items[].namestringCustomer-facing product name.
data.items[].quantityintegerNumber of IPs ordered on this line.
data.items[].duration_daysintegerPurchased duration in days.
data.items[].offer_codestring|nullPublic product identifier, or null when no product link is available.
data.items[].unit_price_centsintegerRecorded per-IP price in minor units.
data.items[].total_price_centsintegerRecorded line total in minor units.
data.deliveryobjectDelivery counts and access state.
data.delivery.expected_countintegerNumber of IPs originally ordered.
data.delivery.delivered_countintegerNumber of connections currently recorded for the order. Check available before using them.
data.delivery.completebooleanWhether the order’s delivery is complete. A completed partial refund can leave fewer delivered IPs than originally ordered.
data.delivery.availablebooleanWhether connections can be read in the current delivery mode.
data.delivery.remaining_countOpt-in fieldintegerOnly with X-PuppyIP-Delivery-Mode: partial. Units still awaiting an outcome after delivered and confirmed refunded units; zero for a final order.
data.poll_after_secondsinteger|nullSuggested delay before checking the order again; null when no further delivery polling is suggested.
data.refundobject|nullRefund progress, or null when no refund information is available. A successful refund can cover only part of an order.
data.refund.statusstring|nullsucceeded, pending, failed or null.
data.refund.destinationstring|nullwallet when the recorded destination is the site wallet; otherwise null. Null does not confirm an external refund.
data.refund.amount_centsinteger|nullRecorded refund amount in minor units, or null when a reliable single amount is unavailable. Null is not zero.
data.refund.currencystring|nullCurrency of the recorded refund amount, or null. Do not substitute the order currency.
data.refund.refunded_atstring|nullRecorded refund time in ISO 8601 UTC, or null.
data.proxiesobject[]Available connection records. The order response uses an empty array until its delivery mode permits access.
data.proxies[].proxy_idstringPublic identity of this proxy resource. Save this ID to query or renew it; connection details may change.
data.proxies[].statusstringCurrent resource status, such as active or expired. Use expires_at as well when checking validity.
data.proxies[].hoststringConnection host or IP address. Use the current returned value.
data.proxies[].portintegerConnection port.
data.proxies[].protocolstring|nullCompatibility field; current products commonly return 0. This value does not select HTTP versus SOCKS5. May be null.
data.proxies[].usernamestring|nullConnection username, or null when unavailable. Treat as sensitive.
data.proxies[].passwordstringConnection password. Treat as sensitive.
data.proxies[].connectionstringConnection text in host:port:username:password format. Treat as sensitive.
data.proxies[].socks5_uristringSOCKS5 connection URI with encoded credentials. Treat as sensitive.
data.proxies[].ip_typestring|nullCurrent codes: 1 = broadcast, 2 = native residential. Historical text values or null may also appear; preserve the returned value.
data.proxies[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.proxies[].regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.
data.proxies[].line_namestring|nullCustomer-facing location or line label; not a stable identifier.
data.proxies[].starts_atstring|nullStart time in ISO 8601 UTC, or null.
data.proxies[].expires_atstring|nullExpiry time in ISO 8601 UTC, or null.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "order_no": "PSO20261109000000EXAMPLE",
        "status": "delivered",
        "payment_status": "paid",
        "payment_method": "wallet",
        "currency": "CNY",
        "subtotal_cents": 2000,
        "total_cents": 2000,
        "total": "20.00",
        "created_at": "2026-11-09T00:00:00+00:00",
        "items": [
            {
                "name": "United States - Los Angeles",
                "quantity": 1,
                "duration_days": 30,
                "offer_code": "off_0123456789abcdef0123456789",
                "unit_price_cents": 2000,
                "total_price_cents": 2000
            }
        ],
        "delivery": {
            "expected_count": 1,
            "delivered_count": 1,
            "complete": true,
            "available": true
        },
        "poll_after_seconds": null,
        "refund": null,
        "proxies": [
            {
                "proxy_id": "px_0123456789abcdef0123456789",
                "status": "active",
                "host": "198.51.100.10",
                "port": 10080,
                "protocol": "0",
                "username": "example_user",
                "password": "example_password",
                "connection": "198.51.100.10:10080:example_user:example_password",
                "socks5_uri": "socks5://example_user:[email protected]:10080",
                "ip_type": "2",
                "country_code": "US",
                "region": "Los Angeles",
                "line_name": "US Los Angeles 2",
                "starts_at": "2026-11-09T00:00:00+00:00",
                "expires_at": "2026-12-09T00:00:00+00:00"
            }
        ]
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}

Order details include order_no, status, payment_status, payment_method, currency, subtotal_cents, total_cents, total, created_at, items, delivery, poll_after_seconds, refund and proxies. Lists return only safe summaries without proxy credentials. refund is null or a refund-status object containing status, destination, amount_cents, currency and refunded_at. refund.status=succeeded confirms the recorded refund, not a full refund of the order; check payment_status for that. An unconfirmed amount, currency or destination is null, which does not mean zero or no refund. Read amount_cents with its own currency; do not infer it from the order currency or current exchange rates.

Order states and sandbox scenarios

GET/api/v1/proxies

List proxies

Retrieve your available proxy connections in pages.

Required scopeproxies:read

Request parameters

FieldLocationRequirementDescription
page_sizeinteger query Optional Items per page: 1–100; default 100 when using pagination.
page_tokenstring query Optional The previous response’s next_page_token, unchanged. Omit for the first page. Maximum 4,096 characters.
X-PuppyIP-Delivery-Modestring header Optional Optional value partial on this GET request. Makes already available connections readable while other units are still pending.
proxy_idstring query Optional Exact proxy_id, up to 80 characters. Combined with proxy_ids[] as an intersection.
order_nostring query Optional Exact PuppyIP order_no, up to 80 characters.
hoststring query Optional Exact returned host, up to 253 characters.
host_containsstring query Optional Literal address fragment, up to 45 characters; hexadecimal characters, dots and colons only. No % or _ wildcards.
statusstring query Optional Exact public status returned by this endpoint, up to 40 characters. An unknown status returns an empty list.
country_codestring query Optional Exact returned country_code, up to 16 characters.
regionstring query Optional Exact returned region, up to 255 characters. If city is also supplied it must match, otherwise HTTP 422.
citystring query Optional Alias for region, up to 255 characters. Both values must match when supplied together.
ip_typestring query Optional Exact returned ip_type value, up to 40 characters.
expires_fromstring query Optional Inclusive start time in ISO 8601 with an explicit timezone. Supports up to 6 fractional digits, four-digit years 0001–9999 and offsets up to ±14:00; compared in UTC.
expires_tostring query Optional Inclusive end time, using the same format as the start; must not precede it.
proxy_ids[]string[] query Optional Up to 100 proxy IDs, each at most 80 characters. Duplicates are removed. Encode as proxy_ids[]=... query parameters.
expiring_soonboolean query Optional true or 1 selects proxies that expire within the next 7 days and have not yet expired. false, 0 or empty disables this filter. Later pages use the first page’s time reference.
  • Use explicit pagination for new integrations. Omitting both pagination parameters in production keeps the legacy limit of 2000 records, not an unlimited list. Filters do not change ownership or expiry-retention rules.
  • The production proxy list retains expired resources for 72 hours after expires_at; resources with a null expiry also remain eligible. This list rule does not impose the same limit on the single-proxy endpoint or sandbox list.
  • Keep connection passwords and complete connection strings in trusted server-side storage, out of public pages and logs. Times use ISO 8601 with a UTC offset; null means unavailable, not zero.
  • Partial delivery is optional on GET requests only. Without the header, the existing delivery behavior and response fields remain unchanged.
  • Pass next_page_token back unchanged and keep the same filters and delivery mode. Tokens expire after 3 days. Continue while has_more is true, even after an empty page. Pages are not a frozen snapshot.
  • Filters are combined. Repeat equivalent filters and delivery mode on later pages; changing or removing them returns invalid_page_token. Date ranges include both endpoints.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/proxies?page_size=100

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.proxiesobject[]Available connection records. The order response uses an empty array until its delivery mode permits access.
data.proxies[].proxy_idstringPublic identity of this proxy resource. Save this ID to query or renew it; connection details may change.
data.proxies[].statusstringCurrent resource status, such as active or expired. Use expires_at as well when checking validity.
data.proxies[].hoststringConnection host or IP address. Use the current returned value.
data.proxies[].portintegerConnection port.
data.proxies[].protocolstring|nullCompatibility field; current products commonly return 0. This value does not select HTTP versus SOCKS5. May be null.
data.proxies[].usernamestring|nullConnection username, or null when unavailable. Treat as sensitive.
data.proxies[].passwordstringConnection password. Treat as sensitive.
data.proxies[].connectionstringConnection text in host:port:username:password format. Treat as sensitive.
data.proxies[].socks5_uristringSOCKS5 connection URI with encoded credentials. Treat as sensitive.
data.proxies[].ip_typestring|nullCurrent codes: 1 = broadcast, 2 = native residential. Historical text values or null may also appear; preserve the returned value.
data.proxies[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.proxies[].regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.
data.proxies[].line_namestring|nullCustomer-facing location or line label; not a stable identifier.
data.proxies[].starts_atstring|nullStart time in ISO 8601 UTC, or null.
data.proxies[].expires_atstring|nullExpiry time in ISO 8601 UTC, or null.
data.page_sizeintegerPage-size limit applied to this response, not the number of returned records.
data.has_morebooleanWhether another page may be requested.
data.next_page_tokenstring|nullOpaque token for the next page; null when there is no next page.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "proxies": [
            {
                "proxy_id": "px_0123456789abcdef0123456789",
                "status": "active",
                "host": "198.51.100.10",
                "port": 10080,
                "protocol": "0",
                "username": "example_user",
                "password": "example_password",
                "connection": "198.51.100.10:10080:example_user:example_password",
                "socks5_uri": "socks5://example_user:[email protected]:10080",
                "ip_type": "2",
                "country_code": "US",
                "region": "Los Angeles",
                "line_name": "US Los Angeles 2",
                "starts_at": "2026-11-09T00:00:00+00:00",
                "expires_at": "2026-12-09T00:00:00+00:00"
            }
        ],
        "page_size": 100,
        "has_more": false,
        "next_page_token": null
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/proxies/{proxy_id}

Get a proxy

Retrieve the latest connection details for a saved proxy ID.

Required scopeproxies:read

Request parameters

FieldLocationRequirementDescription
proxy_idstring path Required A proxy_id returned by an order or proxy response, unchanged. Do not use the host or IP address as this identifier.
X-PuppyIP-Delivery-Modestring header Optional Optional value partial on this GET request. Makes already available connections readable while other units are still pending.
  • Keep proxy_id as your resource key and refresh its connection details when needed. This endpoint returns the proxy object directly inside data, without an environment wrapper.
  • Keep connection passwords and complete connection strings in trusted server-side storage, out of public pages and logs. Times use ISO 8601 with a UTC offset; null means unavailable, not zero.
  • Partial delivery is optional on GET requests only. Without the header, the existing delivery behavior and response fields remain unchanged.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/proxies/px_0123456789abcdef0123456789

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.proxy_idstringPublic identity of this proxy resource. Save this ID to query or renew it; connection details may change.
data.statusstringCurrent resource status, such as active or expired. Use expires_at as well when checking validity.
data.hoststringConnection host or IP address. Use the current returned value.
data.portintegerConnection port.
data.protocolstring|nullCompatibility field; current products commonly return 0. This value does not select HTTP versus SOCKS5. May be null.
data.usernamestring|nullConnection username, or null when unavailable. Treat as sensitive.
data.passwordstringConnection password. Treat as sensitive.
data.connectionstringConnection text in host:port:username:password format. Treat as sensitive.
data.socks5_uristringSOCKS5 connection URI with encoded credentials. Treat as sensitive.
data.ip_typestring|nullCurrent codes: 1 = broadcast, 2 = native residential. Historical text values or null may also appear; preserve the returned value.
data.country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.
data.line_namestring|nullCustomer-facing location or line label; not a stable identifier.
data.starts_atstring|nullStart time in ISO 8601 UTC, or null.
data.expires_atstring|nullExpiry time in ISO 8601 UTC, or null.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "proxy_id": "px_0123456789abcdef0123456789",
        "status": "active",
        "host": "198.51.100.10",
        "port": 10080,
        "protocol": "0",
        "username": "example_user",
        "password": "example_password",
        "connection": "198.51.100.10:10080:example_user:example_password",
        "socks5_uri": "socks5://example_user:[email protected]:10080",
        "ip_type": "2",
        "country_code": "US",
        "region": "Los Angeles",
        "line_name": "US Los Angeles 2",
        "starts_at": "2026-11-09T00:00:00+00:00",
        "expires_at": "2026-12-09T00:00:00+00:00"
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
POST/api/v1/proxies/renew

Renew proxies

Extend selected proxies using your wallet and check each proxy’s result.

Required scopeproxies:write

Request parameters

FieldLocationRequirementDescription
Idempotency-Keystring header Required Required for writes. Use 8–160 characters from A–Z, a–z, 0–9, period, underscore, colon and hyphen. Keep the same key and exact request body when retrying.
X-PuppyIP-Sandbox-Scenariostring header Optional Sandbox renewals only: completed, partial, failed or unresolved; unresolved returns manual_review. partial requires at least 2 proxies. Omit for the existing full-success simulation. Production renewals ignore this header.
proxy_idsstring[] body Required Nonempty list of up to 1000 proxy IDs belonging to your account; maximum 80 characters per ID.
duration_daysinteger body Required Number of days to add: a positive multiple of 30, at most 1,095 days.
syncboolean body Optional Default true keeps synchronous renewal. Set the JSON boolean false for an asynchronous operation when available; acceptance returns HTTP 202 with operation_id and status=processing.
external_order_nostring|null body Optional For sync=false only: optional reference text, up to 100 characters. Saved with the renewal; unlike the purchase reference, this is not a separate deduplication key. The original Idempotency-Key remains required.
remarkstring|null body Optional For sync=false only: optional remark, up to 500 characters. Stored with the operation; not returned in its public result.
  • HTTP 200 does not mean every proxy renewed. Check all three ID lists and line_items. A full failure returns HTTP 422 with the same result structure in data. Unresolved units require reconciliation; do not create another renewal for them.
  • Calculate money with integer minor units or exact decimal arithmetic, not binary floating point. Decimal amount fields are strings with two places.
  • Keep connection passwords and complete connection strings in trusted server-side storage, out of public pages and logs. Times use ISO 8601 with a UTC offset; null means unavailable, not zero.
  • After a timeout, retry with the original Idempotency-Key and identical body bytes, using a fresh timestamp and nonce. Never change the key merely because the result is still pending.
  • Only successful sandbox items gain time. Confirmed failures are refunded; unknown items remain charged and are not automatically renewed again. failed returns HTTP 422 operation_failed with the result in data; other scenarios return 200. The same key replays the original result, and unresolved proxies reject a new renewal.
  • Omitting sync, or setting true, preserves the existing synchronous result. With sync=false, supply distinct proxy_ids. A new operation returns HTTP 202 with data={"operation_id":"rop_...","status":"processing"}; this is not a successful renewal. Poll GET /api/v1/proxies/renewal-operations/{operation_id}. Retrying the original key/body can replay the original acceptance, so query the operation for progress. Production requires asynchronous renewal to be enabled; sandbox uses isolated simulations.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

POST /api/v1/proxies/renew
Content-Type: application/json
Idempotency-Key: example-renew-001

{
    "proxy_ids": [
        "px_0123456789abcdef0123456789"
    ],
    "duration_days": 30
}

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.statusstringcompleted, partial, manual_review or failed. failed is returned with HTTP 422 and success=false; the result remains in data.
data.currencystringCurrency used by this response’s amounts.
data.total_centsintegerQuoted renewal total in minor units before any failed-unit refund.
data.totalstringThe total as a decimal string with two places.
data.charged_centsintegerNet charge after the refunds recorded in this result, in minor units. Pending units can remain charged.
data.refunded_centsintegerAmount refunded for confirmed failures in this result, in minor units.
data.successful_proxy_idsstring[]IDs confirmed renewed.
data.failed_proxy_idsstring[]IDs confirmed not renewed.
data.unresolved_proxy_idsstring[]IDs with an unresolved outcome; do not retry them as a new operation.
data.line_itemsobject[]Outcome and amounts for each requested proxy.
data.proxiesobject[]Current records for the requested proxies; consult the result ID lists to identify which renewals succeeded.
data.walletobjectWallet balance after the operation.
data.line_items[].proxy_idstringPublic identity of this proxy resource. Save this ID to query or renew it; connection details may change.
data.line_items[].hoststringConnection host or IP address. Use the current returned value.
data.line_items[].statusstringsucceeded, failed or unresolved.
data.line_items[].duration_daysintegerRequested extension in days.
data.line_items[].amount_centsintegerQuoted amount for this proxy in minor units.
data.line_items[].amountstringThe same amount as a decimal string with two places.
data.line_items[].charged_centsintegerNet charge after the refunds recorded in this result, in minor units. Pending units can remain charged.
data.line_items[].refunded_centsintegerAmount refunded for confirmed failures in this result, in minor units.
data.proxies[].proxy_idstringPublic identity of this proxy resource. Save this ID to query or renew it; connection details may change.
data.proxies[].statusstringCurrent resource status, such as active or expired. Use expires_at as well when checking validity.
data.proxies[].hoststringConnection host or IP address. Use the current returned value.
data.proxies[].portintegerConnection port.
data.proxies[].protocolstring|nullCompatibility field; current products commonly return 0. This value does not select HTTP versus SOCKS5. May be null.
data.proxies[].usernamestring|nullConnection username, or null when unavailable. Treat as sensitive.
data.proxies[].passwordstringConnection password. Treat as sensitive.
data.proxies[].connectionstringConnection text in host:port:username:password format. Treat as sensitive.
data.proxies[].socks5_uristringSOCKS5 connection URI with encoded credentials. Treat as sensitive.
data.proxies[].ip_typestring|nullCurrent codes: 1 = broadcast, 2 = native residential. Historical text values or null may also appear; preserve the returned value.
data.proxies[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.proxies[].regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.
data.proxies[].line_namestring|nullCustomer-facing location or line label; not a stable identifier.
data.proxies[].starts_atstring|nullStart time in ISO 8601 UTC, or null.
data.proxies[].expires_atstring|nullExpiry time in ISO 8601 UTC, or null.
data.wallet.environmentstringproduction or sandbox.
data.wallet.currencystringCurrency used by this response’s amounts.
data.wallet.balance_centsintegerWallet balance in the currency’s minor units; 100 cents = 1 unit.
data.wallet.balancestringThe same wallet balance as a decimal string with two places.
data.wallet.frozen_centsintegerFrozen wallet amount in minor units.
data.wallet.cash_valuebooleantrue for a production wallet; false for simulated sandbox funds.
data.operation_idOpt-in fieldstringPublic identifier of the asynchronous renewal. Use it to query the operation.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "status": "completed",
        "currency": "CNY",
        "total_cents": 2000,
        "total": "20.00",
        "charged_cents": 2000,
        "refunded_cents": 0,
        "successful_proxy_ids": [
            "px_0123456789abcdef0123456789"
        ],
        "failed_proxy_ids": [],
        "unresolved_proxy_ids": [],
        "line_items": [
            {
                "proxy_id": "px_0123456789abcdef0123456789",
                "host": "198.51.100.10",
                "status": "succeeded",
                "duration_days": 30,
                "amount_cents": 2000,
                "amount": "20.00",
                "charged_cents": 2000,
                "refunded_cents": 0
            }
        ],
        "proxies": [
            {
                "proxy_id": "px_0123456789abcdef0123456789",
                "status": "active",
                "host": "198.51.100.10",
                "port": 10080,
                "protocol": "0",
                "username": "example_user",
                "password": "example_password",
                "connection": "198.51.100.10:10080:example_user:example_password",
                "socks5_uri": "socks5://example_user:[email protected]:10080",
                "ip_type": "2",
                "country_code": "US",
                "region": "Los Angeles",
                "line_name": "US Los Angeles 2",
                "starts_at": "2026-11-09T00:00:00+00:00",
                "expires_at": "2027-01-08T00:00:00+00:00"
            }
        ],
        "wallet": {
            "environment": "production",
            "currency": "CNY",
            "balance_cents": 8000,
            "balance": "80.00",
            "frozen_cents": 0,
            "cash_value": true
        }
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}

POST requests use application/json. Renewal proxy_ids must be a nonempty array of public IDs, with up to 1000 IDs per request. Each ID is limited to 80 characters. Exceeding these limits returns HTTP 422 / validation_error. duration_days must be a positive multiple of 30 and no greater than 1095. Pagination tokens are opaque strings. Responses contain optional fields; clients should tolerate new fields. For unknown business states, stop automatic retries, retain trace_id and contact support.

Renew IPs

POST /api/v1/proxies/renew
Idempotency-Key: renew-demo-001

{
  "proxy_ids": ["SANDBOX_PROXY_20260629080000_001"],
  "duration_days": 30
}

{
  "status": "completed",
  "total_cents": 12000,
  "charged_cents": 12000,
  "refunded_cents": 0,
  "successful_proxy_ids": ["SANDBOX_PROXY_20260629080000_001"],
  "failed_proxy_ids": [],
  "unresolved_proxy_ids": [],
  "line_items": [{
    "proxy_id": "SANDBOX_PROXY_20260629080000_001",
    "host": "198.51.100.10",
    "status": "succeeded",
    "duration_days": 30,
    "amount_cents": 12000,
    "amount": "120.00",
    "charged_cents": 12000,
    "refunded_cents": 0
  }]
}

Existing renewal response fields remain unchanged. The example shows the business data inside data, using example IP values. A status of completed means all items succeeded; partial means partial success, with confirmed failed items refunded to the wallet; manual_review means some results still need review and must not be resubmitted. HTTP 200 or outer success=true does not mean every IP renewed successfully. Check all three proxy_ids groups and each line_items entry.

Each line_items entry contains proxy_id, host, status, duration_days, amount_cents, amount, charged_cents and refunded_cents. Per-item status is succeeded, failed or unresolved. Read the current expires_at for each IP from proxies. Integer amounts are cents; amount/total are decimal strings in the main currency unit. charged_cents is wallet debits minus refunds, not the amount of confirmed successful renewals: unresolved items still need review and are not automatically refunded. Only confirmed failures are refunded; unresolved items are not treated as failed.

If all items conclusively fail, the response remains HTTP 422, success=false and code=operation_failed. data.status=failed, with failed_proxy_ids, line_items, charged_cents=0, refunded_cents and wallet retained. Validation failures or insufficient balance before charging are not this refunded result and may not include renewal details. A replay returns the original result snapshot. Read wallet or proxies separately for the latest balance or expiry.

Completed API idempotency responses are retained for 24 hours. Processing, interrupted or uncertain records are not automatically released for re-execution by expiry or cleanup. If you receive idempotency_unresolved, retain trace_id and contact support for review. Do not use a new key to reorder. Each network retry still uses a new timestamp, nonce and signature, while reusing the same Idempotency-Key and identical raw body.
In production, first call GET /api/v1/offers to obtain production offer_code values, and GET /api/v1/proxies to obtain production proxy_id values; do not mix them with sandbox IDs. Sandbox renewal scenarios are completed, partial, failed and unresolved; unresolved returns manual_review. Failed items are refunded; unknown items are not automatically refunded or renewed again. partial requires at least 2 proxies.
GET/api/v1/locations

List locations

Get public location identifiers from the current catalog.

Required scopeoffers:read

Request parameters

No additional business parameters. Signed request headers are still required.

  • Directories support filtering, not a guarantee of stock for a quantity or duration. Check product quotes and availability, then order with offer_code.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/locations

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.locationsobject[]Locations present in the current catalog.
data.locations[].location_idstringPublic PuppyIP location identifier. Filters products; cannot replace offer_code when ordering.
data.locations[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.locations[].country_namestringCountry display name.
data.locations[].regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "locations": [
            {
                "location_id": "loc_0123456789abcdef0123456789ab",
                "country_code": "US",
                "country_name": "United States",
                "region": "Los Angeles"
            }
        ]
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/locations/tree

Get the location tree

Browse catalog locations grouped by country.

Required scopeoffers:read

Request parameters

No additional business parameters. Signed request headers are still required.

  • Directories support filtering, not a guarantee of stock for a quantity or duration. Check product quotes and availability, then order with offer_code.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/locations/tree

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.countriesobject[]Catalog locations grouped by country.
data.countries[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.countries[].country_namestringCountry display name.
data.countries[].locationsobject[]Locations present in the current catalog.
data.countries[].locations[].location_idstringPublic PuppyIP location identifier. Filters products; cannot replace offer_code when ordering.
data.countries[].locations[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.countries[].locations[].country_namestringCountry display name.
data.countries[].locations[].regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "countries": [
            {
                "country_code": "US",
                "country_name": "United States",
                "locations": [
                    {
                        "location_id": "loc_0123456789abcdef0123456789ab",
                        "country_code": "US",
                        "country_name": "United States",
                        "region": "Los Angeles"
                    }
                ]
            }
        ]
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/tags

List product tags

Get the ordinary and recommended categories present in the catalog.

Required scopeoffers:read

Request parameters

No additional business parameters. Signed request headers are still required.

  • Directories support filtering, not a guarantee of stock for a quantity or duration. Check product quotes and availability, then order with offer_code.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/tags

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.tagsobject[]Product tags used in the current catalog.
data.tags[].codestringordinary or recommended.
data.tags[].nameobjectDisplay names keyed by zh_CN and en.
data.tags[].name.zh_CNstringChinese display name.
data.tags[].name.enstringEnglish display name.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "tags": [
            {
                "code": "recommended",
                "name": {
                    "zh_CN": "推荐",
                    "en": "Recommended"
                }
            }
        ]
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/offers/available

Browse available products

List available products with prices, stock and selectable IP ranges.

Required scopeoffers:read

Request parameters

FieldLocationRequirementDescription
page_sizeinteger query Optional Items per page: 1–200; default 200 when using pagination.
page_tokenstring query Optional The previous response’s next_page_token, unchanged. Omit for the first page. Maximum 4,096 characters.
country_codestring query Optional Match a returned country_code, such as US. Use either country_code or country.
countrystring query Optional Alias for country_code. If both are supplied, both conditions must match.
regionstring query Optional Match a returned region value. Use either region or city.
citystring query Optional Alias for region. If both are supplied, both conditions must match.
linestring query Optional Search the product display name for this text.
ip_typestring query Optional Match the returned IP type. Current codes: 1 = broadcast, 2 = native residential.
quantityinteger query Optional Quantity used to calculate the unit price; default 1, limited to 1–2000. This is not a minimum-stock filter.
duration_daysinteger query Optional Duration used for pricing; default 30 days, limited to 1–1,095. Only products supporting that duration are returned.
line_groupstring query Optional ordinary or recommended. Filters the list before pagination; does not add a response field by itself.
selectionstring query Optional Set to highest_stock to return the same available product list as /offers/available. Omit to use the default /offers list.
location_idstring query Optional A location_id from the location directory: loc_ followed by 26 lowercase hexadecimal characters. A well-formed unknown ID returns an empty list.
tagstring query Optional ordinary or recommended; combined with all other filters.
includestring query Optional Optional extensions: cidr_stock, line_group and catalog; combine with commas. Omit to keep the default response.
  • Quotes use your account pricing, requested quantity and duration; they do not reserve stock. Prices and stock can change before purchase. Do not add stock counts from different products together. A stock value of null means unknown, not zero or unlimited.
  • Calculate money with integer minor units or exact decimal arithmetic, not binary floating point. Decimal amount fields are strings with two places.
  • Pass next_page_token back unchanged and keep the same filters and delivery mode. Tokens expire after 3 days. Continue while has_more is true, even after an empty page. Pages are not a frozen snapshot.
  • The sandbox includes Standard, Recommended and CIDR test products, with per-range stock values of 3, 0 and null. null means unknown, not zero or total stock. Quantity limits apply per request; global stock depletion is not simulated. Returned connections are for testing and do not represent real exits in the selected range.
  • Call this endpoint directly or use /offers?selection=highest_stock. Filter by quantity and duration, and use the price and selectable IP ranges from each product record. If you receive invalid_page_token, restart from the first page. Queries do not reserve stock.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/offers/available?page_size=200

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.currencystringCurrency used by this response’s amounts.
data.payment_modestringwallet. API purchases use the account wallet.
data.offersobject[]Products on this page. An empty array does not end pagination if has_more is true.
data.offers[].offer_codestringPublic product identifier to submit when ordering. Do not parse it.
data.offers[].namestringCustomer-facing product name.
data.offers[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.offers[].country_namestringCountry display name.
data.offers[].regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.
data.offers[].line_namestringCustomer-facing location or line label; not a stable identifier.
data.offers[].ip_typestring|nullCurrent codes: 1 = broadcast, 2 = native residential. Historical text values or null may also appear; preserve the returned value.
data.offers[].protocolstring|nullCompatibility field; current products commonly return 0. This value does not select HTTP versus SOCKS5. May be null.
data.offers[].unit_price_centsintegerPer-IP price in minor units for the requested quantity and duration.
data.offers[].unit_pricestringThe same per-IP price as a decimal string with two places.
data.offers[].base_duration_daysintegerDuration covered by the quoted unit price.
data.offers[].duration_daysinteger[]Supported purchase durations, in days.
data.offers[].max_quantityintegerCurrent total purchase limit for this product, after availability constraints. It is not the stock of every IP range.
data.offers[].cidrsstring[]Selectable IP ranges. An empty array means no specific range is offered.
data.offers[].cidr_stockOpt-in fieldobject<string, integer|null>Only with include=cidr_stock. Maps each CIDR to its available count or null if unknown. Empty maps are {}. Zero means no available stock.
data.offers[].stock_updated_atOpt-in fieldstring|nullOnly with include=cidr_stock. Inventory snapshot time in ISO 8601 UTC, or null. Sandbox returns null.
data.offers[].line_groupOpt-in fieldstringOnly with include=line_group. ordinary or recommended.
data.offers[].location_idOpt-in fieldstring|nullPublic location identifier, or null if unknown. Only with include=catalog.
data.offers[].tagOpt-in fieldstringordinary or recommended. Only with include=catalog.
data.page_sizeintegerPage-size limit applied to this response, not the number of returned records.
data.has_morebooleanWhether another page may be requested.
data.next_page_tokenstring|nullOpaque token for the next page; null when there is no next page.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "currency": "CNY",
        "payment_mode": "wallet",
        "offers": [
            {
                "offer_code": "off_0123456789abcdef0123456789",
                "name": "United States - Los Angeles",
                "country_code": "US",
                "country_name": "United States",
                "region": "Los Angeles",
                "line_name": "United States - Los Angeles",
                "ip_type": "2",
                "protocol": "0",
                "unit_price_cents": 2000,
                "unit_price": "20.00",
                "base_duration_days": 30,
                "duration_days": [
                    30,
                    60,
                    90
                ],
                "max_quantity": 12,
                "cidrs": [
                    "198.51.100.0/24"
                ]
            }
        ],
        "page_size": 200,
        "has_more": false,
        "next_page_token": null
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/announcements

Get announcements

Read the currently published website announcement.

Required scopeaccount:read

Request parameters

FieldLocationRequirementDescription
page_sizeinteger query Optional Items per page: 1–100; default 20 when using pagination.
page_tokenstring query Optional Omit this parameter. The current announcement feed has no next page; a nonempty token returns 422 invalid_page_token.
  • Returns the current website announcement: zero or one entry. has_more is false and next_page_token is null.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/announcements

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.environmentstringproduction or sandbox.
data.announcementsobject[]Currently published announcements; an empty list when none are enabled.
data.announcements[].announcement_idstringPublic identifier for this published announcement.
data.announcements[].titleobjectDisplay names keyed by zh_CN and en.
data.announcements[].contentstringThe announcement text published on the website, unchanged.
data.announcements[].published_atstring|nullPublication update time in ISO 8601 UTC, or null.
data.announcements[].title.zh_CNstringChinese display name.
data.announcements[].title.enstringEnglish display name.
data.page_sizeintegerPage-size limit applied to this response, not the number of returned records.
data.has_morebooleanWhether another page may be requested.
data.next_page_tokenstring|nullOpaque token for the next page; null when there is no next page.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "environment": "production",
        "announcements": [
            {
                "announcement_id": "ann_0123456789abcdef0123456789ab",
                "title": {
                    "zh_CN": "公告",
                    "en": "Announcement"
                },
                "content": "PuppyIP service announcement.",
                "published_at": "2026-11-09T00:00:00+00:00"
            }
        ],
        "page_size": 20,
        "has_more": false,
        "next_page_token": null
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
POST/api/v1/proxies/credentials

Update proxy credentials

Submit a credential update and check the result for each proxy.

Required scopeproxies:credentials

Request parameters

FieldLocationRequirementDescription
Idempotency-Keystring header Required Required for writes. Use 8–160 characters from A–Z, a–z, 0–9, period, underscore, colon and hyphen. Keep the same key and exact request body when retrying.
proxy_idsstring[] body Required 1–100 distinct proxy IDs belonging to your account.
randomboolean body Optional true generates both a username and a password; cannot be combined with username or password.
usernamestring body Optional In nonrandom mode supply at least a username or password. Use 8–32 ASCII letters and digits. Omitted fields keep their old values; if an old value fails this format, supply both new values.
passwordstring body Optional Same rules as username. Keep passwords out of public pages and logs.
  • Requires the separate proxies:credentials permission; existing keys do not gain it automatically. Available in production only when enabled, and in the sandbox with isolated simulated resources.
  • Acceptance returns 202 processing. Replaying the same key and body returns the same operation’s current state; changing the body returns 409 idempotency_key_conflict. A new request overlapping an unresolved operation returns 409 operation_in_progress. Check each proxy’s result. Unresolved updates wait for confirmation without repeating the write. Successful snapshots do not rule out later changes.
  • Keep connection passwords and complete connection strings in trusted server-side storage, out of public pages and logs. Times use ISO 8601 with a UTC offset; null means unavailable, not zero.
  • After a timeout, retry with the original Idempotency-Key and identical body bytes, using a fresh timestamp and nonce. Never change the key merely because the result is still pending.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

POST /api/v1/proxies/credentials
Content-Type: application/json
Idempotency-Key: example-credentials-001

{
    "proxy_ids": [
        "px_0123456789abcdef0123456789"
    ],
    "random": true
}

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.operation_idstringPublic identifier of the credential operation.
data.statusstringprocessing, completed, partial, failed or manual_review.
data.successful_proxy_idsstring[]Proxy IDs confirmed updated.
data.failed_proxy_idsstring[]Proxy IDs confirmed failed.
data.unresolved_proxy_idsstring[]Proxy IDs with an unresolved outcome. Do not submit another update for them.
data.proxiesobject[]Connection snapshots for successful items when this operation was confirmed. Query each proxy for its current connection.
data.proxies[].proxy_idstringPublic identity of this proxy resource. Save this ID to query or renew it; connection details may change.
data.proxies[].statusstringCurrent resource status, such as active or expired. Use expires_at as well when checking validity.
data.proxies[].hoststringConnection host or IP address. Use the current returned value.
data.proxies[].portintegerConnection port.
data.proxies[].protocolstring|nullCompatibility field; current products commonly return 0. This value does not select HTTP versus SOCKS5. May be null.
data.proxies[].usernamestring|nullConnection username, or null when unavailable. Treat as sensitive.
data.proxies[].passwordstringConnection password. Treat as sensitive.
data.proxies[].connectionstringConnection text in host:port:username:password format. Treat as sensitive.
data.proxies[].socks5_uristringSOCKS5 connection URI with encoded credentials. Treat as sensitive.
data.proxies[].ip_typestring|nullCurrent codes: 1 = broadcast, 2 = native residential. Historical text values or null may also appear; preserve the returned value.
data.proxies[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.proxies[].regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.
data.proxies[].line_namestring|nullCustomer-facing location or line label; not a stable identifier.
data.proxies[].starts_atstring|nullStart time in ISO 8601 UTC, or null.
data.proxies[].expires_atstring|nullExpiry time in ISO 8601 UTC, or null.

Response example HTTP 202

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "operation_id": "cop_documentation_example",
        "status": "processing",
        "successful_proxy_ids": [],
        "failed_proxy_ids": [],
        "unresolved_proxy_ids": [
            "px_0123456789abcdef0123456789"
        ],
        "proxies": []
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/proxies/credential-operations/{operation_id}

Get a credential operation

Check confirmed and unresolved results for an operation.

Required scopeproxies:credentials

Request parameters

FieldLocationRequirementDescription
operation_idstring path Required The operation_id returned by the update request. Only your own operations are accessible.
  • Acceptance returns 202 processing. Replaying the same key and body returns the same operation’s current state; changing the body returns 409 idempotency_key_conflict. A new request overlapping an unresolved operation returns 409 operation_in_progress. Check each proxy’s result. Unresolved updates wait for confirmation without repeating the write. Successful snapshots do not rule out later changes.
  • Keep connection passwords and complete connection strings in trusted server-side storage, out of public pages and logs. Times use ISO 8601 with a UTC offset; null means unavailable, not zero.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/proxies/credential-operations/cop_documentation_example

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.operation_idstringPublic identifier of the credential operation.
data.statusstringprocessing, completed, partial, failed or manual_review.
data.successful_proxy_idsstring[]Proxy IDs confirmed updated.
data.failed_proxy_idsstring[]Proxy IDs confirmed failed.
data.unresolved_proxy_idsstring[]Proxy IDs with an unresolved outcome. Do not submit another update for them.
data.proxiesobject[]Connection snapshots for successful items when this operation was confirmed. Query each proxy for its current connection.
data.proxies[].proxy_idstringPublic identity of this proxy resource. Save this ID to query or renew it; connection details may change.
data.proxies[].statusstringCurrent resource status, such as active or expired. Use expires_at as well when checking validity.
data.proxies[].hoststringConnection host or IP address. Use the current returned value.
data.proxies[].portintegerConnection port.
data.proxies[].protocolstring|nullCompatibility field; current products commonly return 0. This value does not select HTTP versus SOCKS5. May be null.
data.proxies[].usernamestring|nullConnection username, or null when unavailable. Treat as sensitive.
data.proxies[].passwordstringConnection password. Treat as sensitive.
data.proxies[].connectionstringConnection text in host:port:username:password format. Treat as sensitive.
data.proxies[].socks5_uristringSOCKS5 connection URI with encoded credentials. Treat as sensitive.
data.proxies[].ip_typestring|nullCurrent codes: 1 = broadcast, 2 = native residential. Historical text values or null may also appear; preserve the returned value.
data.proxies[].country_codestring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.proxies[].regionstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.
data.proxies[].line_namestring|nullCustomer-facing location or line label; not a stable identifier.
data.proxies[].starts_atstring|nullStart time in ISO 8601 UTC, or null.
data.proxies[].expires_atstring|nullExpiry time in ISO 8601 UTC, or null.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "operation_id": "cop_documentation_example",
        "status": "processing",
        "successful_proxy_ids": [],
        "failed_proxy_ids": [],
        "unresolved_proxy_ids": [
            "px_0123456789abcdef0123456789"
        ],
        "proxies": []
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/proxies/renewal-operations/{operation_id}

Get an asynchronous renewal

Check a submitted renewal without starting it again.

Required scopeproxies:write

Request parameters

FieldLocationRequirementDescription
operation_idstring path Required The operation_id returned by an asynchronous renewal. Only your account’s operations in the current environment are accessible.
  • Omitting sync, or setting true, preserves the existing synchronous result. With sync=false, supply distinct proxy_ids. A new operation returns HTTP 202 with data={"operation_id":"rop_...","status":"processing"}; this is not a successful renewal. Poll GET /api/v1/proxies/renewal-operations/{operation_id}. Retrying the original key/body can replay the original acceptance, so query the operation for progress. Production requires asynchronous renewal to be enabled; sandbox uses isolated simulations.
  • While processing, data contains only operation_id and status. A verified result adds the existing renewal amounts, per-item results, wallet and connection snapshot. If the result cannot be verified, manual_review can omit amount and wallet fields; omitted values do not mean zero or a refund. A pre-payment failure can include error and null line-item hosts; a changed quote fails without charging. Only confirmed failures are refundable. Do not create a new operation for unresolved items. Completed results are snapshots, so query each proxy for current connection details. external_order_no and remark are stored only for the asynchronous request and are not added to this response.
  • Keep connection passwords and complete connection strings in trusted server-side storage, out of public pages and logs. Times use ISO 8601 with a UTC offset; null means unavailable, not zero.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/proxies/renewal-operations/rop_documentation_example

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.operation_idstringPublic identifier of the asynchronous renewal. Use it to query the operation.
data.environmentOpt-in fieldstringproduction or sandbox.
data.statusstringprocessing while pending; completed, partial, failed or manual_review when a result is available. This GET returns HTTP 200 even for a failed business result.
data.currencyOpt-in fieldstringCurrency used by this response’s amounts.
data.total_centsOpt-in fieldintegerQuoted renewal total in minor units before any failed-unit refund.
data.totalOpt-in fieldstringThe total as a decimal string with two places.
data.charged_centsOpt-in fieldintegerNet charge after the refunds recorded in this result, in minor units. Pending units can remain charged.
data.refunded_centsOpt-in fieldintegerAmount refunded for confirmed failures in this result, in minor units.
data.successful_proxy_idsOpt-in fieldstring[]IDs confirmed renewed.
data.failed_proxy_idsOpt-in fieldstring[]IDs confirmed not renewed.
data.unresolved_proxy_idsOpt-in fieldstring[]IDs with an unresolved outcome; do not retry them as a new operation.
data.line_itemsOpt-in fieldobject[]Outcome and amounts for each requested proxy.
data.proxiesOpt-in fieldobject[]Current records for the requested proxies; consult the result ID lists to identify which renewals succeeded.
data.walletOpt-in fieldobjectWallet balance after the operation.
data.line_items[].proxy_idOpt-in fieldstringPublic identity of this proxy resource. Save this ID to query or renew it; connection details may change.
data.line_items[].hostOpt-in fieldstring|nullConnection host or IP address. Use the current returned value.
data.line_items[].statusOpt-in fieldstringsucceeded, failed or unresolved.
data.line_items[].duration_daysOpt-in fieldintegerRequested extension in days.
data.line_items[].amount_centsOpt-in fieldintegerQuoted amount for this proxy in minor units.
data.line_items[].amountOpt-in fieldstringThe same amount as a decimal string with two places.
data.line_items[].charged_centsOpt-in fieldintegerNet charge after the refunds recorded in this result, in minor units. Pending units can remain charged.
data.line_items[].refunded_centsOpt-in fieldintegerAmount refunded for confirmed failures in this result, in minor units.
data.proxies[].proxy_idOpt-in fieldstringPublic identity of this proxy resource. Save this ID to query or renew it; connection details may change.
data.proxies[].statusOpt-in fieldstringCurrent resource status, such as active or expired. Use expires_at as well when checking validity.
data.proxies[].hostOpt-in fieldstringConnection host or IP address. Use the current returned value.
data.proxies[].portOpt-in fieldintegerConnection port.
data.proxies[].protocolOpt-in fieldstring|nullCompatibility field; current products commonly return 0. This value does not select HTTP versus SOCKS5. May be null.
data.proxies[].usernameOpt-in fieldstring|nullConnection username, or null when unavailable. Treat as sensitive.
data.proxies[].passwordOpt-in fieldstringConnection password. Treat as sensitive.
data.proxies[].connectionOpt-in fieldstringConnection text in host:port:username:password format. Treat as sensitive.
data.proxies[].socks5_uriOpt-in fieldstringSOCKS5 connection URI with encoded credentials. Treat as sensitive.
data.proxies[].ip_typeOpt-in fieldstring|nullCurrent codes: 1 = broadcast, 2 = native residential. Historical text values or null may also appear; preserve the returned value.
data.proxies[].country_codeOpt-in fieldstring|nullCountry code, or null when unavailable. Use the returned value for filtering.
data.proxies[].regionOpt-in fieldstring|nullRegion or city, or null when unavailable. Use the returned value for filtering.
data.proxies[].line_nameOpt-in fieldstring|nullCustomer-facing location or line label; not a stable identifier.
data.proxies[].starts_atOpt-in fieldstring|nullStart time in ISO 8601 UTC, or null.
data.proxies[].expires_atOpt-in fieldstring|nullExpiry time in ISO 8601 UTC, or null.
data.wallet.environmentOpt-in fieldstringproduction or sandbox.
data.wallet.currencyOpt-in fieldstringCurrency used by this response’s amounts.
data.wallet.balance_centsOpt-in fieldintegerWallet balance in the currency’s minor units; 100 cents = 1 unit.
data.wallet.balanceOpt-in fieldstringThe same wallet balance as a decimal string with two places.
data.wallet.frozen_centsOpt-in fieldintegerFrozen wallet amount in minor units.
data.wallet.cash_valueOpt-in fieldbooleantrue for a production wallet; false for simulated sandbox funds.
data.errorOpt-in fieldobjectPresent for a failure before a payment was taken; charged_cents and refunded_cents are both zero.
data.error.codeOpt-in fieldstringrenewal_price_changed, insufficient_wallet_balance or operation_failed.
data.error.messageOpt-in fieldstringSafe explanation of the failed admission or execution.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "operation_id": "rop_documentation_example",
        "status": "processing"
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
GET/api/v1/webhook

Get webhook settings

Read the callback subscription configured for this API key.

Required scopewebhooks:write

Request parameters

No additional business parameters. Signed request headers are still required.

  • Each API key manages its own subscription and needs the separate webhooks:write permission; older keys do not gain it automatically. When disabled in production, POST configuration returns 403 feature_disabled; existing settings can still be read or disabled. Sandbox configuration and signature documentation are available for testing, but actual outbound delivery also requires sending to be enabled. A successful configuration response is not proof that sending is active.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

GET /api/v1/webhook

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.configuredbooleanWhether this API key has an enabled subscription; this alone does not confirm outbound delivery is enabled.
data.urlstring|nullConfigured callback URL, or null when disabled.
data.eventsstring[]Subscribed event names; empty when disabled.
data.versionintegerConfiguration revision, incremented when the subscription changes.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "configured": true,
        "url": "https://example.com/puppyip-webhook",
        "events": [
            "order.updated",
            "renewal.updated",
            "proxy.updated"
        ],
        "version": 1
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
POST/api/v1/webhook

Configure a webhook

Choose an HTTPS callback URL and the events you want to receive.

Required scopewebhooks:write

Request parameters

FieldLocationRequirementDescription
Idempotency-Keystring header Required Required for writes. Use 8–160 characters from A–Z, a–z, 0–9, period, underscore, colon and hyphen. Keep the same key and exact request body when retrying.
urlstring body Required HTTPS URL, up to 2,048 characters, on a publicly resolvable domain. Port 443 only; no username, password or fragment in the URL. All resolved addresses must be public.
eventsstring[] body Required 1–3 distinct values from order.updated, renewal.updated and proxy.updated.
  • Each API key manages its own subscription and needs the separate webhooks:write permission; older keys do not gain it automatically. When disabled in production, POST configuration returns 403 feature_disabled; existing settings can still be read or disabled. Sandbox configuration and signature documentation are available for testing, but actual outbound delivery also requires sending to be enabled. A successful configuration response is not proof that sending is active.
  • Verify with the API Secret of the key that configured this webhook. X-PuppyIP-Timestamp is Unix time in seconds. Compute lowercase hexadecimal HMAC-SHA256 over PuppyIP-Webhook-v1\n<timestamp>\n<raw JSON body>, with each \n representing one LF byte. Compare with X-PuppyIP-Signature using a constant-time comparison; validate the timestamp against your accepted window and confirm X-PuppyIP-Event-ID equals body.event_id. Verify the raw bytes before parsing or re-encoding JSON.
  • Only HTTP 200 acknowledges delivery. Return 200 for an already handled event_id as well. Delivery can repeat up to 10 attempts, with the same event_id and body but a fresh signing timestamp. Deduplicate event_id and do not assume arrival order or exactly-once delivery. A notification is a prompt to read current API state, not independent proof of payment or permission to charge again. Revoking the key, disabling the subscription or changing its configuration can cancel pending deliveries.
  • The event object contains event_id, event, environment, occurred_at, observed_at, reason and data; it does not use the API success envelope. event is order.updated, renewal.updated or proxy.updated. Multiple changes to the same object may be combined into one notification. occurred_at is when that notification was first triggered; observed_at is when its reported state was observed. Retries keep the same body. Notifications may omit intermediate states.
  • order.updated data: order_no, status, payment_status, currency and total_cents. proxy.updated data: proxy_id, status and expires_at. renewal.updated data: status, currency, total_cents, charged_cents, refunded_cents and the successful_proxy_ids, failed_proxy_ids and unresolved_proxy_ids lists. Unverified renewal amounts can be null, not zero. Notifications contain no connection passwords or connection strings; fetch current details with an authorized API request.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

POST /api/v1/webhook
Content-Type: application/json
Idempotency-Key: example-configure-webhook-001

{
    "url": "https://example.com/puppyip-webhook",
    "events": [
        "order.updated",
        "renewal.updated",
        "proxy.updated"
    ]
}

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.configuredbooleanWhether this API key has an enabled subscription; this alone does not confirm outbound delivery is enabled.
data.urlstring|nullConfigured callback URL, or null when disabled.
data.eventsstring[]Subscribed event names; empty when disabled.
data.versionintegerConfiguration revision, incremented when the subscription changes.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "configured": true,
        "url": "https://example.com/puppyip-webhook",
        "events": [
            "order.updated",
            "renewal.updated",
            "proxy.updated"
        ],
        "version": 1
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}
DELETE/api/v1/webhook

Disable a webhook

Disable the callback subscription for this API key.

Required scopewebhooks:write

Request parameters

FieldLocationRequirementDescription
Idempotency-Keystring header Required Required for writes. Use 8–160 characters from A–Z, a–z, 0–9, period, underscore, colon and hyphen. Keep the same key and exact request body when retrying.
  • DELETE requires a normal signed request, webhooks:write and Idempotency-Key. Within the 24-hour completed-response replay window, retry with the same API key, idempotency key and original body to replay that disable operation without disabling a subscription configured afterward. Use a new idempotency key for a new disable action. Normal API polling remains available.

Request example

The example shows the path and business parameters. Include signed headers in the actual request. View signing examples

DELETE /api/v1/webhook
Idempotency-Key: example-disable-webhook-001

Response fields

The table lists the complete response. Endpoint-specific business data is nested under data. View response envelope

FieldTypeDescription
successbooleanWhether this API operation succeeded. Still check the order or renewal result for each unit’s outcome.
codestringStable result code. Successful responses use ok.
messagestringHuman-readable result message. Successful responses use OK.
dataobjectThe response object for this endpoint.
timestampstringResponse time in ISO 8601 format, UTC.
trace_idstringRequest reference to include when contacting support.
data.configuredbooleanWhether this API key has an enabled subscription; this alone does not confirm outbound delivery is enabled.
data.urlstring|nullConfigured callback URL, or null when disabled.
data.eventsstring[]Subscribed event names; empty when disabled.
data.versionintegerConfiguration revision, incremented when the subscription changes.

Response example HTTP 200

{
    "success": true,
    "code": "ok",
    "message": "OK",
    "data": {
        "configured": false,
        "url": null,
        "events": [],
        "version": 2
    },
    "timestamp": "2026-11-09T00:00:00+00:00",
    "trace_id": "trc_documentation_example"
}

Stable pagination

GET /api/v1/offers?page_size=200

{
  "offers": [],
  "page_size": 200,
  "has_more": true,
  "next_page_token": "opaque-token-from-response"
}

For the next page, pass the returned next_page_token unchanged as page_token and keep the same page_size and filters, for example GET /api/v1/offers?page_size=200&page_token=.... Route, order and IP lists use the same response fields. Routes allow up to 200 entries per page; order and IP page limits are enforced by their respective endpoints. Tokens are bound to the current account, environment, resource type and route filters. Tampered, expired, cross-account tokens or tokens used with changed filters are rejected.

Stop paging only when has_more=false. If has_more=true, continue with next_page_token even when offers is empty or contains fewer than page_size entries. Pages read current data, not a frozen catalog snapshot. Periodically restart from the first page, deduplicating and updating by offer_code.

Order states and sandbox scenarios

After order creation returns 201, use the returned PuppyIP order_no to query GET /api/v1/orders/{order_no}.paid or procuring means processing. When poll_after_seconds is an integer, wait that many seconds before the next query. Stop polling when it is null or the order reaches a terminal state. For HTTP 429, follow Retry-After first. The proxies in order details may be delivered to the customer only when delivery.available=true .

{
  "order_no": "SANDBOX_ORDER_EXAMPLE",
  "status": "procuring",
  "payment_status": "paid",
  "delivery": {
    "expected_count": 3,
    "delivered_count": 1,
    "complete": false,
    "available": false
  },
  "poll_after_seconds": 1,
  "refund": null,
  "proxies": []
}

No scenario header: Immediate success with all test IPs, compatible with existing sandbox clients.

success: Normal asynchronous completion with all test IPs.

delayed: Completion after a few seconds, for testing the polling interface.

partial: Partial progress first, followed by complete delivery. By default, order responses do not include credentials before completion, while sandbox proxy lists and details can still return test IPs that have been delivered. Enabling the read mode below also returns delivered IPs in order responses and shows the remaining delivery count.

rejected: No IP delivery. The test charge is automatically returned to the test wallet, and refund explicitly reports the destination and status.

Partial-delivery read mode (optional)

GET /api/v1/orders/{order_no}
X-PuppyIP-Delivery-Mode: partial

Both production and sandbox support this optional header only for GET /api/v1/orders, GET /api/v1/orders/{order_no}, GET /api/v1/proxies and GET /api/v1/proxies/{proxy_id}. If omitted or not set to partial, each environment retains its existing response fields, statuses and read behavior. Production retains the complete-order delivery gate. Sandbox order responses retain that gate, while sandbox proxy lists and details retain their account- and sandbox-identifier-scoped access to test resources. Existing clients need no changes. This does not affect POST requests, raw JSON signatures, Idempotency-Key or historical response replays.

When enabled, paid, delivered IPs still owned by the current account can be read immediately. Unpaid, fully refunded, missing or transferred resources remain unavailable. delivery.available=true means resources are currently available; only delivery.complete=true means the entire order is delivered. Orders may show partially_delivered. procurement_blocked still means the outcome is unsettled, not full success; continue following poll_after_seconds.

Only with this mode enabled, delivery in order details and lists adds delivery.remaining_count. While unsettled, it equals the original expected quantity minus currently delivered IPs and confirmed per-item refund quantities, with a minimum of 0. It is 0 after complete fulfillment, a full refund or cancellation. Missing resources from historically completed orders are not counted as pending delivery. expected_count retains the original purchased quantity; see refund separately for partial refunds. Order lists still do not return proxy credentials.

Pagination tokens are bound to the delivery read mode. Keep the same header when paging. Switching modes requires starting again from the first page; otherwise the response is 422 invalid_page_token. Deleting a revoked API key does not erase unexpired completed responses or unsettled idempotency records. Retrying with a new key must still use the original Idempotency-Key and raw body. Changing credentials must not re-execute the same purchase.

Common errors

invalid_signature
Missing signature, incorrect App ID, inconsistent canonical string construction or the wrong API Secret.
stale_timestamp
The timestamp lacks a timezone, is not ISO 8601, or differs from the server clock by more than 5 minutes.
replayed_nonce
The same nonce was reused with one API key. Generate a new random nonce for every request.
idempotency_key_required
A request requiring Idempotency-Key is missing the header, or its length or characters are invalid.
idempotency_key_conflict
The same Idempotency-Key has already been used with a different request body.
idempotency_processing / idempotency_replay_failed
The operation is still processing, or the original response cannot yet be safely replayed. Keep the same key and raw body, then retry later as directed by the response.
idempotency_unresolved
HTTP 409. The earlier request may already have charged funds or produced another outcome and requires review. Do not assume an error or timeout means nothing executed, and do not automatically retry with a new key.
insufficient_scope
The API key lacks the required permission, or the request's source IP is outside the allowed range.
api_key_revoked
The API key has been revoked. Use a valid key with the required permissions.
validation_error
Request fields, quantity, duration or combinations do not meet endpoint constraints. Correct them according to data.errors before resubmitting.
offer_not_found / order_not_found / proxy_not_found
The public identifier does not exist, is unavailable or does not belong to the current account and environment. Retrieve the relevant list again.
insufficient_wallet_balance
Insufficient wallet balance. The order or renewal was not successfully charged.
invalid_page_token
The pagination token has expired, been modified or been used with the wrong account, environment or resource. Start again from the first page.
rate_limited
Requests are too frequent. Read Retry-After before retrying. Do not poll continuously without a delay.
unsupported_operation
The operation is not available in Customer API v1, such as IP replacement or trial IPs.
proxy_endpoint_unavailable / operation_failed
The connection endpoint is unavailable or the operation is incomplete. Keep trace_id and follow the retry guidance for the response status, or contact support.