# CatProxies

> CatProxies sells residential, ISP, datacenter, mobile and IPv6 proxies, with a
> self-serve dashboard and two REST APIs. Operated by MOTAN DATA SOLUTIONS S.R.L.,
> Romania.

This file is a map. If you are an AI assistant and you want everything in one
fetch, get https://catproxies.com/llms-full.txt instead.

## Documentation

Two separate APIs. Each has a complete Markdown version written to be read by an
AI assistant: fetch the `llms-full.txt` that matches the reader and you have
everything needed to integrate.

- [User API, complete](https://catproxies.com/docs/api/llms-full.txt): for a normal customer automating their own account at retail prices. Human page: https://catproxies.com/docs/api
- [Reseller API, complete](https://catproxies.com/docs/llms-full.txt): for partners reselling at wholesale prices under their own brand. Human page: https://catproxies.com/docs
- [Everything, one file](https://catproxies.com/llms-full.txt): this page plus both API references.
- [Machine index of every page](https://catproxies.com/sitemap.md)

## Products

- [Residential Proxies](https://catproxies.com/Proxies/residential): Rotating residential proxies from real home connections, priced per GB.
- [ISP Proxies](https://catproxies.com/Proxies/isp): Static ISP-assigned IPs from a shared pool, priced per IP per month.
- [Dedicated ISP Proxies](https://catproxies.com/Proxies/dedicated-isp): Private static ISP IPs nobody else uses, priced per IP per month.
- [Datacenter Proxies](https://catproxies.com/Proxies/datacenter): Fast dedicated datacenter IPs with a shared bandwidth pool.
- [Mobile Proxies](https://catproxies.com/Proxies/mobile): Rotating IPs on real mobile carrier networks, priced per GB.
- [IPv6 Proxies](https://catproxies.com/Proxies/ipv6): A very large IPv6 pool, the cheapest way to make a lot of requests.

## Solutions

- [Social Media](https://catproxies.com/Solutions/social-media): Managing multiple social accounts without them being linked.
- [SEO Monitoring](https://catproxies.com/Solutions/seo-monitoring): Accurate search results from the location you care about.
- [Market Research](https://catproxies.com/Solutions/market-research): Competitive pricing and product data at scale.
- [Cyber Security](https://catproxies.com/Solutions/cyber-security): Security research, testing and network monitoring.
- [Ad Verification](https://catproxies.com/Solutions/ad-verification): Checking how ads actually render in other countries.
- [Ad Tech](https://catproxies.com/Solutions/ad-tech): Proxy infrastructure for ad tech platforms.

## Use cases

- [Retail](https://catproxies.com/use-cases/retail): Stock and pricing across retail sites.
- [Sneakers](https://catproxies.com/use-cases/sneaker): Limited sneaker releases and the sites that host them.
- [Pokémon Center](https://catproxies.com/use-cases/pokemon-center): Trading card drops and restocks.

## Comparisons

- [PyProxy Alternative](https://catproxies.com/alternatives/pyproxy): What happened to PyProxy after the January 2026 IPIDEA takedown, and a dated side-by-side comparison of prices, targeting, sessions and sourcing.
- [NetNut Alternative](https://catproxies.com/alternatives/netnut): NetNut's July 2026 domain seizure and netnut.ai relaunch, CatProxies test data on NetNut exit IPs, and a dated comparison of prices, static IPs and sourcing.
- [922 S5 Proxy Alternative](https://catproxies.com/alternatives/922-s5-proxy): 922 S5 Proxy's status after Google's January 2026 IPIDEA takedown, its sister brands and lookalike sites, and what replaces its SOCKS5 IPs and desktop client.
- [Oxylabs Alternative](https://catproxies.com/alternatives/oxylabs): Oxylabs compared with CatProxies Premium Residential, a pool built from several independent networks behind one login: prices per GB, targeting, sessions and trials.
- [Asocks Alternative](https://catproxies.com/alternatives/asocks): Asocks compared with CatProxies on price per GB, pool size, static IPs and sourcing, with a dated, sourced record of the May 2026 Dutch botnet takedown and Asocks' response.

## Company

- [Pricing](https://catproxies.com/pricing): Prices for every product.
- [Reseller Program](https://catproxies.com/become-reseller): Wholesale pricing for partners reselling under their own brand.
- [Affiliate Program](https://catproxies.com/affiliate): Commission for sending customers our way.
- [Partners](https://catproxies.com/partner): Tools and services we work with.
- [Status](https://catproxies.com/status): Live service status.
- [Contact](https://catproxies.com/contact-us): How to reach support.

## Free tools

- [IP Lookup and IP Quality Check](https://catproxies.com/tools/ip-lookup): Free IP lookup: country, city, ASN and ISP, residential, ISP, mobile or datacenter, proxy and VPN detection, blocklists and a trust score. Works for IPv4 and IPv6.
- [Proxy Checker](https://catproxies.com/tools/proxy-checker): Free bulk proxy checker for HTTP and SOCKS5: exit IP, speed, anonymity, sticky or rotating, and tests against popular sites.
- [IPv6 Compatibility Checker](https://catproxies.com/compatibility-checker/ipv6): Check whether a target site can be reached over IPv6.

## Blog

- [Proxy Providers Linked to Botnets](https://catproxies.com/Blog/proxy-providers-linked-to-botnets): Which proxy providers were tied to botnets in 2026 and where each stands now: NetNut, IPIDEA, PYPROXY and IPWeb, with domain checks run October 2, 2026.
- [How to Add a Proxy to Octo Browser](https://catproxies.com/Blog/how-to-setup-catproxies-in-octobrowser): A step by step guide to adding CatProxies proxies to an Octo Browser profile, with proxy format, protocol, the built-in proxy check, and a Pixelscan test.
- [How to Set Up a Proxy in Vision Browser](https://catproxies.com/Blog/how-to-set-up-catproxies-in-vision-browser): Step by step guide to adding CatProxies proxies to a Vision Browser profile, with proxy format, protocol, geo database, and p0f device detection tips.
- [Best Proxies for Pokemon Center Drops](https://catproxies.com/Blog/best-proxies-for-pokemon-center-drops): Learn which proxies actually win Pokemon Center drops. Static ISP, mobile, and residential compared for session stability, trust, and checkout success.
- [Why Proxies Still Get CAPTCHAs](https://catproxies.com/Blog/why-proxies-still-get-captchas): A hands-on test of why a clean ISP proxy still triggered CAPTCHAs on nine of twelve sites, and how a CAPTCHA solver cleared them. Real numbers and code.
- [How to Set Up a Proxy in Dolphin Anty](https://catproxies.com/Blog/how-to-set-up-catproxies-in-dolphin-anty): A step by step guide to adding CatProxies residential and static ISP proxies to a Dolphin Anty browser profile, with session and protocol tips.
- [What Happened to NetNut: Our Test Data From Before the Takedown](https://catproxies.com/Blog/netnut-alternative-our-test-data-and-what-the-takedown-means): NetNut's domains were seized in July 2026. CatProxies tested NetNut exit IPs weeks earlier: only 44% were residential. See the data and what it means.
- [Inside a 25 Million Request L7 DDoS Attack: A CatProxies Case Study](https://catproxies.com/Blog/inside-a-25-million-request-l7-ddos-attack-a-catproxies-case-study): A firsthand L7 DDoS attack case study: 25.7 million requests in 24 hours, why signature WAF rules matched zero events, and what actually stopped it.
- [Best Datacenter Proxies 2026: Real Tested Comparison](https://catproxies.com/Blog/best-datacenter-proxies-2026-real-tested-comparison): Best datacenter proxies in 2026 compared on verified unique USA IP counts from ProxyWay, with real pricing and documented fair usage limits.
- [IPv4 vs IPv6 Proxies: Honest Comparison](https://catproxies.com/Blog/ipv4-vs-ipv6-proxies-honest-comparison): IPv4 vs IPv6 proxies compared by a working operator: real prices, site compatibility, speed, country targeting, and which one to pick for your use case.
- [IPv6 to IPv4 Conversion: How It Actually Works](https://catproxies.com/Blog/ipv6-to-ipv4-conversion-how-it-actually-works): IPv6 to IPv4 conversion explained simply: address notation, NAT64 limits, and when an IPv4 proxy is the better answer than any translator.
- [How to Use Residential Proxies: A Practical Setup Guide](https://catproxies.com/Blog/how-to-use-residential-proxies-a-practical-setup-guide): A hands-on guide to setting up residential proxies the right way, with authentication, targeting, session control, and the mistakes that cause most failures.
- [Spying as an Art: Survival in Arbitrage in 2026](https://catproxies.com/Blog/spying-as-an-art-survival-in-arbitrage-in-2026): Survive arbitrage in 2026 with competitive intelligence, monitoring tools, automation, and data-driven strategies for staying ahead.
- [Sticky vs Rotating Proxies: Differences and Best Uses [2026]](https://catproxies.com/Blog/sticky-vs-rotating-proxies-differences-and-best-uses-2026): Sticky proxies hold the same IP for hours. Rotating proxies assign a new IP per request. Compare session behavior and use cases to pick the right mode
- [Rank Tracking Proxies](https://catproxies.com/Blog/rank-tracking-proxies): Rank tracking proxies route SERP checks through neutral IPs for accurate, location-specific keyword data. Learn proxy types, limitations, and cost planning.
- [ISP vs Residential Proxies: Differences and Best Uses [2026]](https://catproxies.com/Blog/isp-vs-residential-proxies-differences-and-best-uses-2026): ISP proxies offer static IPs with datacenter speed. Residential proxies offer rotating IPs from real devices. Compare speed, pricing, and detection risk.
- [How to Set Up Proxies in MostLogin [2026]](https://catproxies.com/Blog/how-to-set-up-proxies-in-mostlogin-2026): Step by step guide to setting up residential proxies in MostLogin antidetect browser. Covers proxy configuration, fingerprint testing, and common mistakes.
- [CloakingHouse Review [2026]: Setup, Proxies & How It Works](https://catproxies.com/Blog/cloakinghouse-review-2026-setup-proxies-how-it-works): CloakingHouse Review (2026): Features, setup process, pricing, and how it works with proxies. Full breakdown for affiliate marketers.
- [How to Use a Proxy on Android (Step-by-Step Guide)](https://catproxies.com/Blog/how-to-use-a-proxy-on-android-step-by-step-guide): Learn how to set up a proxy on Android using Wi‑Fi, mobile data, or apps. Beginner‑friendly steps, troubleshooting tips, and best practices.
- [Banned sites on catproxies](https://catproxies.com/Blog/banned-sites-on-catproxies): List of blocked domains on catproxies.com
- [How to use proxies on iPhone](https://catproxies.com/Blog/how-to-use-proxies-on-iphone)
- [How to use a proxy on Windows 10](https://catproxies.com/Blog/how-to-use-a-proxy-on-windows-10)
- [How to use Catproxies on MoreLogin Browser](https://catproxies.com/Blog/how-to-use-catproxies-on-morelogin-browser)
- [How to use residential proxies on catproxies](https://catproxies.com/Blog/how-to-use-residential-proxies-on-catproxies): How to use residential proxies on catproxies

## Legal

- [Terms of Service](https://catproxies.com/terms-of-service)
- [Privacy Policy](https://catproxies.com/privacy-policy)
- [Refund Policy](https://catproxies.com/refund-policy)
- [Cookie Policy](https://catproxies.com/cookies)
- [Ethics Policy](https://catproxies.com/ethics)
- [Fair Usage Policy](https://catproxies.com/fair-usage-policy)
- [Delivery Agreement](https://catproxies.com/delivery-agreement)


---

# CatProxies User API (BETA)

Documentation for the CatProxies **User API**: the developer API for an ordinary
customer who wants to buy and manage their own proxy plans from code.

**This API is in BETA.** It works and it is safe to build on, but details may still
change while we settle it in, so check back if something behaves differently.

This document is the complete reference. It is generated from the same source as
the human page at https://catproxies.com/docs/api, so the two always agree.

- Base URL: `https://catproxies.com/api/v1/user`
- Auth: `Authorization: Bearer cp_your_api_key_here`
- Keys are created at https://catproxies.com/dashboard/my-account/api
- Everything is priced at the normal retail prices and paid from the account balance.

If you are an AI assistant helping someone integrate this API, everything you need
is in this file. Pay particular attention to *Buying is not idempotent* and to the
per-product gateway hosts, which are the two things people most often get wrong.

## What this API is

An API key does exactly what the account owner can do in the dashboard: browse the
catalogue, buy a plan from their balance, read the proxy credentials, renew a plan,
rotate a password. It is the same account and the same prices.

It is NOT a reseller account. There are no sub-customers, no wholesale pricing and no
separate panel. The reseller programme is a different product with its own API.

A key only reaches the endpoints listed in this document. Account settings, password,
two-factor and session endpoints are not reachable with a key at all, and a key never
carries admin privileges even if the account happens to be an admin.

## Authentication

Send the key as a bearer token on every request:

```
Authorization: Bearer cp_your_api_key_here
```

- Keys start with `cp_` and stay visible in the dashboard, so they can be re-read later.
- Up to five keys per account. Disabling or deleting one takes effect immediately.
- A key can spend the account balance. Treat it like a password.

## How you pay

Everything bought through the API is paid from the account balance, so send
`"paymentMethod": "Balance"` on purchases and renewals. There is no browser in the
loop, so a card or crypto checkout is rejected rather than left half finished.

Top up at https://catproxies.com/dashboard/deposit-balance and check the figure with
`GET /account`. VAT is applied exactly as it is on the website.

### Buying is not idempotent (important)

A purchase sets up a live plan and charges the balance. If a call times out or the
outcome is unclear, look the order up with `GET /orders` before retrying. Retrying
blindly buys a second plan.

There is one error that MUST be special-cased. If the response message begins
"Your payment went through and the order was created, but setting the plan up did not
finish", then the balance HAS been charged and the order exists. The payload carries
`OrderId`. Do not order again; the order is completed or refunded on our side.

## Quick start

```bash
KEY="cp_your_api_key_here"
API="https://catproxies.com/api/v1/user"

# 1. Check the balance
curl -s -H "Authorization: Bearer $KEY" "$API/account"

# 2. Find a plan to buy
curl -s -H "Authorization: Bearer $KEY" "$API/packages?proxyType=v2resi&pageSize=50"

# 3. Buy it (paid from the account balance). Returns payload.OrderId.
curl -s -X POST "$API/purchase" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "PACKAGE_ID_FROM_STEP_2", "paymentMethod": "Balance" }'

# 4. Read the credentials
curl -s -H "Authorization: Bearer $KEY" "$API/orders/ORDER_ID_FROM_STEP_3"

# 5. Use them. The HOST is a fixed gateway that belongs to the PRODUCT,
#    not to the order. See the per-product section below.
curl -x resi-us.catproxies.com:9000 \
  -U "USERNAME-type-residential-country-us:PASSWORD" https://ipinfo.io
```

### The single most common mistake

`proxy.server_ip` on `GET /orders/:id` is NOT the host to connect to. It is null on
most products. The host is a fixed gateway per product, listed in the per-product
sections below. Custom ISP and Dedicated ISP are the exception: there each
IP is its own line with its own host, port and login, all from `GET /generator/isp`.

## Rate limits

1000 requests per hour per key, on a rolling window. Every response carries:

| Header | Meaning |
| --- | --- |
| `X-RateLimit-Limit` | Requests allowed per hour |
| `X-RateLimit-Remaining` | How many are left |
| `X-RateLimit-Reset` | When capacity returns (ISO 8601) |

Going over returns `429` with a `Retry-After` header.

On top of that, these three share ONE bucket of 3 requests per minute, counted per
account rather than per key, and the account owner's own dashboard clicks count too:

| Endpoint | Limit |
| --- | --- |
| `POST /purchase` | 3 per minute (shared) |
| `POST /purchase/extend` | 3 per minute (shared) |
| `POST /orders/:id/reset-password` | 3 per minute (shared) |

Nothing is created or charged when that limit refuses a call. Put a short pause
between orders rather than firing them in parallel.

## Errors

Errors are JSON with a readable `message`, usually alongside `"type": "error"`.

One exception worth coding for: a bad or inactive API key (401), an endpoint an
API key may not use (403) and the hourly key budget (429) answer with
`{"status": 401, "statusText": "..."}` instead. The readable text is in
`statusText`, not `message`, so read both fields when you log an error.

**Branch on the HTTP status, not on `type`.** A few handled failures answer 200
with an error body, and a few 404s still carry `type: "response"`, so the status
is the only reliable signal.

| Status | Meaning | What to do |
| --- | --- | --- |
| 400 | The request was wrong | Read `message`. Usually a missing or invalid field. |
| 401 | Bad or disabled key | Check the key is correct and still active. |
| 403 | Not available to API keys | That endpoint is dashboard only. |
| 404 | Not found | The order or package id does not exist on this account. |
| 409 | A renewal is running or under review | Datacenter, IPv6 and Custom ISP renewals. Nothing was charged for this request. Read the message: wait for the running renewal, or contact us if it is under review. |
| 429 | Rate limited | Wait for `Retry-After`, then continue. |
| 500 or 502 | Our side failed | Safe to retry a read. Do NOT blindly retry a purchase or a renewal: read the message first (see below), and check /orders or /orders/:id before sending it again. |

### Messages worth handling in code

| Message | What it means |
| --- | --- |
| Insufficient balance | Top up and retry. Nothing was created. |
| Your payment went through and the order was created, but setting the plan up did not finish | The balance HAS been charged and the order exists. `payload.OrderId` identifies it. Do NOT retry. |
| Please add a name and address to complete your profile | The account needs a name and address before it can buy, because invoices are per order. |
| API orders are paid from your account balance | A paymentMethod other than "Balance" was sent. |
| This plan type cannot be renewed at the moment | Plans on a retired product. Buy a new plan instead. |
| A renewal for this plan is already in progress | HTTP 409, Datacenter, IPv6 and Custom ISP. A second renewal was sent while the first was still running. It was not charged. Wait for the first to finish and check GET /orders/:id before renewing again. |
| This plan's last renewal is being checked by our team | HTTP 409, Datacenter, IPv6 and Custom ISP. An earlier renewal is under manual review. Nothing was charged for this request. Do not renew again yet; contact us. |
| Failed to extend proxy plan. Your balance has been refunded. | HTTP 500 on a renewal. Nothing happened and the money is back on the balance. Safe to try again later. |
| Your renewal payment went through, but we need to confirm it with a quick manual check | HTTP 500, Datacenter, IPv6 and Custom ISP. The balance WAS charged; our team applies the renewal within 24 hours or refunds it. Do NOT retry. A retry only gets the 409 above, so it cannot charge twice. |
| You can only extend with a Datacenter plan of the same size | The packageId has a different IP count or bandwidth from the Datacenter plan. Pick one from `GET /orders/:id/extend-options`. |
| You can only renew this IPv6 plan with... | The packageId is the wrong kind for the IPv6 plan (unlimited vs bandwidth, or a different speed or length). Pick one from `GET /orders/:id/extend-options`. |
| An IPv6 plan can hold at most 5000 GB... | The bandwidth package would take the plan past 5000 GB in total. Nothing was charged. Pick a smaller one from `GET /orders/:id/extend-options`. |
| Please choose one of the available quantity options for this plan | Dedicated ISP. The quantity must match one of the offered sizes. |
| This city is out of stock right now / This carrier is out of stock in this city right now / Only 3 IPs are available for this choice right now | Custom ISP. Stock ran out since the catalog was read. Nothing was charged. Read `GET /custom-isp/catalog` (or `/carriers`) again and pick another city, carrier or a lower quantity. |
| You have no IP replacements left on this plan | Custom ISP. Every replacement bought with the plan has been used. |
| Package not found | On a Custom ISP purchase, send a packageId from `GET /custom-isp/catalog`, not the `customisp` marker from `/packages`. On a Datacenter or IPv6 renewal it means packageId is missing, unknown or no longer on sale. |
| Not enough IPs available | Datacenter. Allocate within the stock from `/packages/datacenterp-countries`. |
| Too many requests (HTTP 429) | The shared 3-per-minute bucket. Wording differs between endpoints, so branch on the 429 status, not the text. Nothing was created. |
| Rate limit exceeded | The hourly budget for this key is used up. |

Errors returned by a proxy while using it, rather than by this API: `407` is bad
credentials or a non-whitelisted address, and `429` from a proxy means too many
connections at once.

## Products

### Products and their proxyType

Every plan has a `proxyType`. Use it to filter the catalogue (`/packages?proxyType=...`) and to tell which product an order is. Each one has its own guide further down this page.

| Product | proxyType | Sold by | Renewable |
| --- | --- | --- | --- |
| Standard Residential | `v2resi` | Bandwidth (GB) | Yes |
| Premium Residential | `resix` | Bandwidth (GB) | Yes |
| Custom ISP | `Isp` | Per IP, 1 day to 3 months, by city | Yes |
| Dedicated ISP | `IspP` | Per IP, per month | Yes |
| Rotating Mobile | `RotatingMobile` | Bandwidth (GB) | Yes |
| Datacenter | `DatacenterP` | IPs plus a bandwidth pool | Yes |
| IPv6 | `Ipv6p` | Bandwidth, or unlimited with a speed cap | Yes |

Custom ISP plans, prices and cities come from `GET /custom-isp/catalog`, not from `/packages`.

### Connecting, per product

Buying a plan is the same call for every product. What differs is the extra body the purchase needs, the gateway and the ports you connect to, and where the targeting flags go. Each guide below is complete on its own: find your product, and everything you need is in that one block.

`GET /orders/:id` gives you the credentials for a plan, as `proxy.username` and `proxy.password`. The **host** is a fixed gateway that belongs to the product, not to your order, and each guide lists them. Do not build your proxy string from `proxy.server_ip`: it is only populated on some products and is not the host you should connect to.

The exceptions are Custom ISP and Dedicated ISP, where every IP is its own line with its own host, port and login, all of which come from `GET /generator/isp`.

### What can be renewed

Renewing keeps an existing plan alive rather than creating a new one, so the credentials stay the same. Not every product supports it right now.

| Product | Renewable | What to send |
| --- | --- | --- |
| Standard Residential | Yes | orderId, paymentMethod and the packageId of the size you want |
| Premium Residential | Yes | orderId, paymentMethod and the packageId of the size you want |
| Rotating Mobile | Yes | orderId, paymentMethod and the packageId of the size you want |
| Custom ISP | Yes | orderId and paymentMethod only. It renews the same plan (same length, same IPs) at its per-IP price, plus the carrier fee per IP when the plan has a carrier. IP replacements are not renewed. Check the cost with GET /purchase/isp-extend-price. |
| Dedicated ISP | Yes | orderId and paymentMethod only. It renews the same IPs for another 30 days. |
| Datacenter | Yes | orderId, paymentMethod and the packageId of a package with the same IPs and bandwidth; only the length may differ. Get it, and the exact price, from GET /orders/:id/extend-options. Renewing resets your data to the plan's full GB for the new term: any data you haven't used yet doesn't carry over, but your remaining days do. Add-ons are charged again at +25% each. |
| IPv6 | Yes | orderId, paymentMethod and the packageId of a plan of the same kind, from GET /orders/:id/extend-options: for unlimited plans the unlimited package with the same speed and the same length, a bandwidth package for bandwidth plans (its GB are added, and the plan can hold at most 5000 GB in total). |

Whatever you send, the amount charged and the bandwidth or days added are always worked out on our side from the package itself, never from the request body. Anything else you put in the body is ignored.

The full renewal rules for each product are in the Renewing part of its guide below.

### Country, state and city lists

Where to get the list of places you can target, per product. Some are a live
endpoint, some are one downloadable file, and two products have no list because
the country you get IS the gateway you connect to.

| Product | What you can target | Where the list comes from |
| --- | --- | --- |
| Standard Residential | Country, US state, city, ASN | Live: GET /generator/budget/targeting-options?orderId=<your order>. Returns the countries available to your plan, with their states and cities. |
| Premium Residential | 196 countries, 50 US states, cities in 201 countries | Download: https://catproxies.com/files/premium_residential_x_targeting.json. It carries the whole reference in one file: countries, us_states and a cities map keyed by country code, plus the exact flag format and the ports. |
| Rotating Mobile | Country, region or state, city, carrier | Live, four endpoints: GET /generator/mobile-locations/countries, then /regions, /cities and /isps for the place you picked. Offline copy of the same tree: https://catproxies.com/files/mobile_targeting.json (242 countries). |
| Custom ISP | City, and optionally the carrier and UDP support, chosen when you buy | Live: GET /custom-isp/catalog?tier=...&udp=... lists the countries and cities in stock with IP counts. GET /custom-isp/carriers?location=... lists the carriers (ASNs) in one city. |
| Dedicated ISP | None | Pool based, so there is no country to choose and locations[] is empty. |
| Datacenter | US, CA, DE, GB, NL | Fixed set of five. Live stock per country: GET /packages/datacenterp-countries. You split your IPs across them at purchase with datacenterPData.country_proxies. |
| IPv6 | Worldwide, US, DE, GB, NL | Fixed set of five, and the country IS the gateway you connect to: ipv6-ww, ipv6-us, ipv6-de, ipv6-gb, ipv6-nl .catproxies.com. Same credentials on all of them. |

Things that trip people up:

- **Standard Residential:** Values go on the USERNAME, lowercase, with no spaces or separators: -country-us-state-california-city-losangeles. It is scoped to your own order, so ask for it once at startup and cache it.
- **Premium Residential:** Values go on the PASSWORD, and the first flag joins with a dash while every one after it joins with an underscore. Multi-word cities have no separator at all: _city-losangeles.
- **Rotating Mobile:** Each entry is an object with a code and a name: use the code. Multi-word values join with a plus sign, so -city-los+angeles and -isp-at&t+wireless. The four endpoints share 60 requests per 10 minutes, so fetch them once and cache.
- **Custom ISP:** Send the city exactly as the catalog returns it ("Country - City") as ispData.location, and a carrier's asn as ispData.asn. Ask with the same tier and udp you will buy, since stock differs per tier and per UDP choice. Fixed at purchase; an IP replacement stays in the same city.
- **Datacenter:** The split must add up to exactly the IP count of the package. A worldwide rotating line only works on a plan that owns IPs in all five.
- **IPv6:** Nothing to buy per country and nothing to send: point at a different gateway and you are in a different country.

### Standard Residential (`v2resi`)

*Real home IPs, sold by the gigabyte.*

#### What it is

Sold by bandwidth. Buy it with nothing but the packageId. One set of credentials covers every country and session; you choose where an IP comes from with flags on the username. A fresh IP on every request by default.

#### Buying

```bash
curl -X POST https://catproxies.com/api/v1/user/purchase \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "PACKAGE_ID", "paymentMethod": "Balance" }'
```

#### Proxy format and connecting

GET /orders/:id gives you proxy.username and proxy.password. The host is one of the three fixed regional gateways below: pick the one nearest your target for lower latency. The same credentials work on all of them. Ignore proxy.server_ip on this product; it is not set.

| Gateway | Rotating port | Sticky port |
| --- | --- | --- |
| resi-us.catproxies.com (United States) | 9000 HTTP / 11000 SOCKS5 | 10000 HTTP / 12000 SOCKS5 |
| resi-fr.catproxies.com (Europe) | 9000 HTTP / 11000 SOCKS5 | 10000 HTTP / 12000 SOCKS5 |
| resi-sg.catproxies.com (Asia) | 9000 HTTP / 11000 SOCKS5 | 10000 HTTP / 12000 SOCKS5 |

**Example**

```bash
# From GET /orders/:id
#   username: mtpeqbcevnemewc201120   password: jonxybfeuu
# Host: pick the gateway nearest your target from the table above

# Rotating IP in the US, a new one on every request (HTTP port 9000)
curl -x resi-us.catproxies.com:9000 \
  -U "mtpeqbcevnemewc201120-type-residential-country-us:jonxybfeuu" \
  https://ipinfo.io

# The same IP in Los Angeles for 30 minutes (sticky HTTP port 10000)
curl -x resi-us.catproxies.com:10000 \
  -U "mtpeqbcevnemewc201120-type-residential-country-us-state-california-city-losangeles-lifetime-30-session-ab12:jonxybfeuu" \
  https://ipinfo.io

# SOCKS5, rotating, through the Europe gateway
curl -x socks5h://resi-fr.catproxies.com:11000 \
  -U "mtpeqbcevnemewc201120-type-residential-country-de:jonxybfeuu" \
  https://ipinfo.io
```

#### Targeting, appended to the username

Leave the password alone and append flags to the username. -type-residential is required; everything else is optional.

| Target | Flag | Value format |
| --- | --- | --- |
| Type (required) | `-type-residential` | always include this |
| Country | `-country-us` | ISO-2 code, lowercase |
| State or region | `-state-california` | lowercase, no spaces |
| City | `-city-losangeles` | lowercase, no spaces or separators |
| ASN or carrier | `-asn-7922` | ASN number only |
| Sticky session | `-lifetime-30-session-ab12` | minutes (1 to 1440) plus any id of your choosing |
| UDP over SOCKS5 | `-requireUdp-true` | SOCKS5 only, on port 12000 |

- Without the sticky flags you get a new IP on every request. Add -lifetime-<minutes>-session-<id> to hold one IP, up to 1440 minutes (24 hours). Use a different session id per line to hold several sticky IPs at once.
- Valid countries, states, cities and ASNs come from GET /generator/budget/targeting-options?orderId=... Cache that list; it is large and changes rarely.
- If a very specific combination returns nothing, that spot is momentarily empty. Broaden the targeting: drop the city first, then the state.

#### Renewing

Yes. POST /purchase/extend with the orderId and the packageId of the size you want.

#### Good to know

- **Where these hosts come from:** These gateways are fixed, and the same list is on the plan page in your dashboard, which is always current. If we ever move a gateway the hostname stays the same, so build your proxy strings from the names above rather than resolving one to an IP and hard-coding that.
- **Bandwidth:** proxy.bandwidth_left on GET /orders/:id is what you have left, in GB. When it hits zero the plan stops passing traffic even if the date has not been reached.
- **Authentication:** Username and password only. There is no IP whitelist on this product.
- **Rotate the password:** POST /orders/:id/reset-password. Targeting lives on the username here, so only the password half of your proxy strings changes.

### Premium Residential (`resix`)

*A cleaner residential pool for the sites that keep blocking you.*

#### What it is

Sold by bandwidth and bought exactly like Standard Residential, with nothing but the packageId. Two things differ: the flags go on the password rather than the username, and you pick your region by which gateway you connect to.

#### Buying

```bash
curl -X POST https://catproxies.com/api/v1/user/purchase \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "PACKAGE_ID", "paymentMethod": "Balance" }'
```

#### Proxy format and connecting

GET /orders/:id gives you proxy.username and proxy.password. The host is one of the regional gateways below, and unlike our other products the HTTP and SOCKS5 gateways are different names, so take the row for the protocol you want. The same credentials work on all of them, including the six dedicated ISP pools at the bottom of the table.

| Region | HTTP gateway | SOCKS5 gateway |
| --- | --- | --- |
| Americas | presix.catproxies.com:6011 | presix-socks.catproxies.com:6014 |
| Europe | presix-eu.catproxies.com:6012 | presix-eu-socks.catproxies.com:6015 |
| Asia, Africa and Oceania | presix-asia.catproxies.com:6013 | presix-asia-socks.catproxies.com:6016 |
| ISP pool: United States | usax.catproxies.com:6000 | Not available (HTTP only) |
| ISP pool: United States (Tickets) | usax-tk.catproxies.com:6001 | Not available (HTTP only) |
| ISP pool: United Kingdom | ukx.catproxies.com:6003 | Not available (HTTP only) |
| ISP pool: Germany | dex.catproxies.com:6006 | Not available (HTTP only) |
| ISP pool: Germany (DTAG) | dex-dtag.catproxies.com:6007 | Not available (HTTP only) |
| ISP pool: Australia | aux.catproxies.com:6002 | Not available (HTTP only) |

**Example**

```bash
# From GET /orders/:id
#   username: hftjvbtwgxxxceb205604   password: ngnstzylne
# Host: the gateway for your region and protocol, from the table above

# Worldwide, rotating (bare password, Americas HTTP gateway)
curl -x presix.catproxies.com:6011 \
  -U "hftjvbtwgxxxceb205604:ngnstzylne" https://ipinfo.io

# UK, London, with the quality filter (Europe HTTP gateway).
# First flag joins with "-", the rest with "_".
curl -x presix-eu.catproxies.com:6012 \
  -U "hftjvbtwgxxxceb205604:ngnstzylne-country-gb_city-london_streaming-1" \
  https://ipinfo.io

# Two different sticky IPs: one unique 8-character session id per line
curl -x presix.catproxies.com:6011 -U "hftjvbtwgxxxceb205604:ngnstzylne-country-us_session-a1b2c3d4_lifetime-1h" https://ipinfo.io
curl -x presix.catproxies.com:6011 -U "hftjvbtwgxxxceb205604:ngnstzylne-country-us_session-e5f6g7h8_lifetime-1h" https://ipinfo.io

# SOCKS5 uses its own gateway name and port
curl -x socks5h://presix-socks.catproxies.com:6014 \
  -U "hftjvbtwgxxxceb205604:ngnstzylne-country-us" https://ipinfo.io

# Device targeting (best effort), always the LAST flag
curl -x presix-eu.catproxies.com:6012 \
  -U "hftjvbtwgxxxceb205604:ngnstzylne-country-de_session-a1b2c3d4_lifetime-1h_device-windows" \
  https://ipinfo.io

# Dedicated ISP pool (UK): HTTP only, no country or other flags, sticky session optional
curl -x ukx.catproxies.com:6003 \
  -U "hftjvbtwgxxxceb205604:ngnstzylne-session-a1b2c3d4_lifetime-1h" https://ipinfo.io
```

#### Targeting, appended to the password

Leave the username alone and append flags to the password. The first flag joins the password with a hyphen and every flag after it with an underscore. All of them are optional; a bare password gives you a worldwide IP.

| Target | Flag | Value format |
| --- | --- | --- |
| Country | `-country-us` | ISO-2 code, lowercase. Leave it out for worldwide. |
| US state | `_state-california` | lowercase, no spaces (newyork). US only, and not with a city. |
| City | `_city-losangeles` | lowercase, no spaces. Needs a country, and not with a state. |
| Sticky session | `_session-a1b2c3d4` | exactly 8 lowercase letters or digits. One id means one IP. |
| Session lifetime | `_lifetime-1h` | how long that IP is held: minutes or hours (30m, 1h, 24h), up to 168h |
| Quality filter | `_streaming-1` | best effort cleaner IPs |
| Speed filter | `_direct-1` | best effort lower latency |
| Auth filter | `_skipispstatic-1` | best effort fewer blocks |
| Device | `_device-windows` | windows, android, ios, macos or linux. Best effort. Put it last. |

- Mind the joiners: the FIRST flag attaches to the password with a hyphen, every one after it with an underscore.
- City and state are mutually exclusive and both need a country. Use one or the other, never both.
- Without a _session- flag you get a fresh IP for every new connection (a connection your client keeps open keeps its IP). Add a unique 8-character session id per line, optionally with _lifetime-, to hold a batch of different sticky IPs.
- The three filters are best effort, not guarantees. Stacking several of them narrows the pool, so use only the one you actually need.
- Device targeting is best effort too. It asks for IPs from that type of device and most come back that way, but not all. It narrows the pool, so combining it with a city, a state or the filters (the speed filter the most) means more failed connections. Only the five listed values work: any other value makes the connection hang with no error.
- The dedicated ISP pools take no flags except a sticky session: send the bare password (rotating) or PASSWORD-session-a1b2c3d4_lifetime-1h. Country, city, state, filters and device do not apply there.
- Any city in the world can be asked for, but the pool moves, so a very small city may fall back to the wider region. Prefer larger cities.

#### Renewing

Yes. POST /purchase/extend with the orderId and the packageId of the size you want.

#### Good to know

- **Where these hosts come from:** These gateways are fixed, and the same list is on the plan page in your dashboard, which is always current. If we ever move a gateway the hostname stays the same, so build your proxy strings from the names above rather than resolving one to an IP and hard-coding that.
- **Dedicated ISP pools:** Six fast, country-locked ISP pools (United States, United States Tickets, United Kingdom, Germany, Germany DTAG, Australia) come with every Premium Residential plan, on the same login and bandwidth. They are part of Premium Residential, not the separate Dedicated ISP product. The host decides the pool and its country. They are HTTP only and take no targeting flags. They are also in the Dedicated ISP pool dropdown on the plan page.
- **Bandwidth:** proxy.bandwidth_left on GET /orders/:id is what you have left, in GB.
- **Authentication:** Username and password only. There is no IP whitelist on this product.
- **Rotate the password:** POST /orders/:id/reset-password changes the BASE password only. Rebuild your strings with the new base and re-append the same flags, since targeting lives on the password here.

### Custom ISP (`Isp`)

*ISP IPs in the city you pick, on the carrier you pick.*

#### What it is

ISP proxies sold per IP, where you choose everything: the tier (Standard, or Clean Score with IPs that have a fraud score of 0), the plan length (1 day, 7 days, 1 month, 2 months or 3 months), the city, and if you want, the carrier and UDP support. You can also buy up to 5 IP replacements with the plan, to swap an IP later. Price = price per IP x IPs, plus the carrier fee per IP x IPs when you pick a carrier, plus replacements x the replacement price. VAT is added on top. The plans and cities come from GET /custom-isp/catalog, not from /packages.

#### Buying

```bash
# 1. Plans, prices and cities in stock (tier=premium for Clean Score, udp=1 for UDP only)
curl "https://catproxies.com/api/v1/user/custom-isp/catalog?tier=standard" \
  -H "Authorization: Bearer cp_your_api_key_here"
# -> plans.standard["1_month"].packageId, pricePerIp, minQty, maxQty, replacementPrice
# -> countries[].cities[].location, e.g. "United States - New York City"

# 2. Optional: carriers in that city (same tier and udp as you will buy)
curl -G "https://catproxies.com/api/v1/user/custom-isp/carriers" \
  --data-urlencode "location=United States - New York City" \
  -H "Authorization: Bearer cp_your_api_key_here"
# -> carriers[].asn, e.g. 7922

# 3. Buy 5 IPs on that carrier, with 2 IP replacements
curl -X POST https://catproxies.com/api/v1/user/purchase \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "packageId": "PACKAGE_ID_FROM_STEP_1",
    "paymentMethod": "Balance",
    "ispData": {
      "quantity": 5,
      "location": "United States - New York City",
      "asn": 7922,
      "udp": false,
      "replacements": 2
    }
  }'
```

#### Proxy format and connecting

GET /generator/isp?orderId=... returns one line per IP. Every line uses the same connection server and the same port, 1337, for both HTTP and SOCKS5. The plan has one password, and each line's login carries its IP, so build each proxy string from its own line. Pick the connection server with &region=: auto (the default) is the one closest to you, and connection_regions in the reply lists the others. The exit IP does not change with the region, only the route to it.

| Field | Meaning |
| --- | --- |
| host | the connection server for the region you picked (the same on every line) |
| port_http / port_socks | both 1337. HTTP, HTTPS and SOCKS5 all run on the same port. |
| login / password | the login is different on every line (it carries the line's IP), the password is the same |
| ip | the IP this line exits from. Send it to the replace endpoint to swap it. |

**Example**

```bash
# From GET /generator/isp?orderId=...&region=us-east
#   { "host": "us-east.isp.catproxies.com", "port_http": 1337, "port_socks": 1337,
#     "login": "cpx8f31a2-203.0.113.24", "password": "Rk4pX9wq", "ip": "203.0.113.24" }

# HTTP and HTTPS
curl -x us-east.isp.catproxies.com:1337 -U "cpx8f31a2-203.0.113.24:Rk4pX9wq" https://ipinfo.io

# SOCKS5, same port
curl -x socks5h://us-east.isp.catproxies.com:1337 -U "cpx8f31a2-203.0.113.24:Rk4pX9wq" https://ipinfo.io

# Proxy string format for tools that want host:port:login:password
#   us-east.isp.catproxies.com:1337:cpx8f31a2-203.0.113.24:Rk4pX9wq

# Swap 203.0.113.24 for a new IP in the same city (uses one replacement)
curl -X POST https://catproxies.com/api/v1/user/custom-isp/orders/ORDER_ID/replace \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "ip": "203.0.113.24" }'
```

#### Targeting

There is nothing to append. The city, carrier and UDP choice are fixed when you buy, and each line is one fixed IP.

#### Renewing

Yes. POST /purchase/extend with just the orderId and paymentMethod, no packageId: it renews the same plan (same length, same IPs) at its per-IP price, plus the carrier fee per IP again when the plan has a carrier. IP replacements are not renewed. Check the cost first with GET /purchase/isp-extend-price?orderId=... One renewal per plan at a time. If only some IPs could be renewed, the renewal still succeeds and our team completes the rest; do not renew again.

#### Good to know

- **Tiers:** standard is Standard. premium is Clean Score: the IPs are checked right before delivery and have a fraud score of 0 when you get them. clean_score on GET /generator/isp is true on these plans.
- **Do not check IPs through the proxy:** We check every IP before delivery. Please do not visit IP check or fraud score sites (IPQS and similar) through the proxy: those sites log every visit, and it can raise the IP's fraud score. To check an IP, look it up from another device or a connection that is not using the proxy.
- **IP locations:** Locations (country and city) are strictly based on the ipinfo.io database. Other IP databases can show a different city for the same IP.
- **location:** Send the city exactly as the catalog returns it, as "Country - City". The catalog counts are at most a minute old, so the purchase checks stock again and refuses cleanly (nothing charged) when a city or carrier ran out.
- **Carrier (asn):** Optional. Take it from GET /custom-isp/carriers for the same city, tier and udp. It adds carrierFee per IP. Without it you get IPs from any carrier in that city.
- **IP replacements:** 0 to 5, bought with the plan at its replacementPrice each. POST /custom-isp/orders/:id/replace with { ip } swaps that IP for a new one in the same city; the city and UDP support stay, but for now the carrier (ASN) can be different. Renewing keeps the same IPs. HTTP 202 with { checking: true } means the swap is still being confirmed: do not send it again. Replacements belong to that plan and are not added again when you renew. IP replacements can only be bought together with the order: they can't be added later, so choose how many you need when you buy.
- **Personal price and discounts:** pricePerIp in the catalog already includes any launch discount and your personal price. Coupons are refused when either applies (customRate true, or promoPct above 0).
- **Authentication:** Username and password only. There is no IP whitelist and no password reset on this product.
- **Just after buying:** The reply can say settingUp while the IPs are placed, and GET /generator/isp can answer HTTP 202 { pending: true }. Wait a few seconds and ask again. Do not buy again.
- **Stock problems and errors:** Sometimes a city or carrier runs out, or delivery fails. Stock is checked before you pay, so most of these are refused with nothing charged. If it fails after you paid, the plan shows as being set up and we retry it automatically: after about 1, 3, 6 and 11 minutes, then every 10 minutes for up to 6 hours. If it is still not delivered, or the result is unclear, our team checks whether the IPs were delivered and finishes the plan or refunds it within 24 hours. Do not buy again in the meantime.

### Dedicated ISP (`IspP`)

*Private static IPs, nobody else on them.*

#### What it is

Sold per IP, per month, and the IPs are yours alone rather than from a shared pool. There is no country to choose, and the term is a fixed 30 days. The quantity is not free-form: it has to match one of the sizes offered for the plan.

#### Buying

```bash
# Dedicated ISP takes the quantity only,
# and that quantity must be one of the sizes the plan offers (see /packages).
curl -X POST https://catproxies.com/api/v1/user/purchase \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "packageId": "PACKAGE_ID",
    "paymentMethod": "Balance",
    "ispData": { "quantity": 5 }
  }'
```

#### Proxy format and connecting

GET /generator/isp?orderId=... returns one line per IP with its own host, port, login and password.

| Field | Meaning |
| --- | --- |
| host | the IP address of that line |
| port_http | HTTP and HTTPS port for that line |
| port_socks | SOCKS5 port for that line, and UDP rides on it too. It is NOT the same number as port_http on this product, so always read this field. Connecting SOCKS5 to the HTTP port fails outright. |
| login / password | credentials for that line only |

**Example**

```bash
# From GET /generator/isp?orderId=...
#   { "host": "203.0.113.24", "port_http": 61234, "port_socks": 62234,
#     "login": "ded_4f21ba", "password": "Kq7ZmTx2" }

# HTTP and HTTPS on port_http
curl -x 203.0.113.24:61234 -U "ded_4f21ba:Kq7ZmTx2" https://ipinfo.io

# SOCKS5, and UDP over SOCKS5, on port_socks. Same host, DIFFERENT port.
curl -x socks5h://203.0.113.24:62234 -U "ded_4f21ba:Kq7ZmTx2" https://ipinfo.io
```

#### Targeting

There is nothing to append. Each IP is a fixed address and the product is pool based, so there is no country flag either.

#### Renewing

Yes. POST /purchase/extend with just the orderId and paymentMethod: it renews the same number of IPs for another 30 days. Check the cost first with GET /purchase/isp-extend-price?orderId=...

#### Good to know

- **Username and password only:** There is no IP-authentication option on this product.
- **IP locations:** Locations are strictly based on the ipinfo.io database. Other IP databases can show a different location for the same IP.
- **stale:** stale: true on GET /generator/isp means the list is our last known good copy. The lines still work; call again shortly for a fresh list.
- **Just after buying:** The lines are normally ready immediately. If the list comes back empty on the first call, give it a few seconds and ask again.
- **Allowed quantities:** Only the sizes listed for the plan in GET /packages?proxyType=IspP are accepted. Anything else is refused with 'Please choose one of the available quantity options for this plan.'
- **Term:** A fixed 30 days per purchase, whatever quantity you buy.

### Datacenter (`DatacenterP`)

*A block of dedicated IPs plus a shared bandwidth pool.*

#### What it is

One plan gives you a list of dedicated IPs and a shared GB pool for a fixed number of days, for example 100 IPs with 250 GB for 30 days. At purchase you decide how those IPs split across US, CA, DE, GB and NL. Every IP is its own proxy and they all share one username and password.

#### Buying

```bash
# country_proxies must add up to EXACTLY the package's IP count.
# Check live stock first with GET /packages/datacenterp-countries.
curl -X POST https://catproxies.com/api/v1/user/purchase \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "packageId": "PACKAGE_ID",
    "paymentMethod": "Balance",
    "datacenterPData": {
      "country_proxies": { "US": 60, "DE": 20, "GB": 20 },
      "high_concurrency": false,
      "high_priority": false,
      "whitelisted_ips": false
    }
  }'
```

#### Proxy format and connecting

GET /orders/:id returns proxy.username and proxy.password (shared by every IP) plus ip_list, grouped by country and then city. Flatten cities[].ips to get every address. The list is refreshed on each call. There are two ways to connect: STATIC, where you connect to one of your own IPs and always exit from it, and ROTATING, where you connect to a country gateway below, add -ttl-0 to the username and get a new IP from your plan on every new connection. The same credentials work on every IP and every gateway.

| Gateway | HTTP port | SOCKS5 port |
| --- | --- | --- |
| dcww.catproxies.com (worldwide rotating, only on plans with all five countries) | 1338 | 1339 |
| dcus.catproxies.com (United States rotating) | 1338 | 1339 |
| dcca.catproxies.com (Canada rotating) | 1338 | 1339 |
| dcde.catproxies.com (Germany rotating) | 1338 | 1339 |
| dcgb.catproxies.com (United Kingdom rotating) | 1338 | 1339 |
| dcnl.catproxies.com (Netherlands rotating) | 1338 | 1339 |
| Any IP from your ip_list (static) | 1338 | 1339 |

**Example**

```bash
# From GET /orders/:id
#   username: dcuser482913   password: dcpass91827
#   ip_list -> 203.0.113.24, 203.0.113.57, ...
#   rotating.endpoints -> { "US": "dcus.catproxies.com", "DE": "dcde.catproxies.com" }

# STATIC: that exact IP every time
curl -x 203.0.113.24:1338 -U "dcuser482913:dcpass91827" https://ipinfo.io

# ROTATING, United States: a new US IP from your plan on every new connection
curl -x dcus.catproxies.com:1338 -U "dcuser482913-ttl-0:dcpass91827" https://ipinfo.io

# ROTATING, Germany
curl -x dcde.catproxies.com:1338 -U "dcuser482913-ttl-0:dcpass91827" https://ipinfo.io

# ROTATING, worldwide (only on a plan that owns IPs in ALL FIVE countries)
curl -x dcww.catproxies.com:1338 -U "dcuser482913-ttl-0:dcpass91827" https://ipinfo.io

# SOCKS5 on port 1339, static and rotating
curl -x socks5h://203.0.113.24:1339 -U "dcuser482913:dcpass91827" https://ipinfo.io
curl -x socks5h://dcus.catproxies.com:1339 -U "dcuser482913-ttl-0:dcpass91827" https://ipinfo.io

# Proxy string format for tools that want host:port:user:pass
#   static:    203.0.113.24:1338:dcuser482913:dcpass91827
#   rotating:  dcus.catproxies.com:1338:dcuser482913-ttl-0:dcpass91827
```

#### Targeting, appended to the username

The country is decided by the address you connect to: one of your own IPs for static, or that country's gateway for rotating. The only flag is the one that turns a line from static into rotating.

| Target | Flag | Value format |
| --- | --- | --- |
| Rotating | `-ttl-0` | a new IP on every new connection, from your own IPs in that country |

- Without the flag you get that exact IP every time, which is the default.
- Only use a gateway for a country your plan has IPs in. A gateway for a country you did not buy answers 407.
- proxy.rotating.endpoints on GET /orders/:id gives the exact rotating host for each country of your plan. It is usually the gateway in the table above. It gives you a plain IP instead when your plan owns one of the gateway addresses, or in the US when all your US IPs sit behind just one of the two US addresses. Reading it from the API is the safest option.
- Do not use one of your own IPs as the rotating host. If an IP is whitelisted, a request through one of your own IPs is logged in by IP, the flag is ignored and you always get that IP.
- proxy.rotating.bound_ip_conflicts lists the countries where your plan owns the only gateway address. Rotating there keeps giving the same IP while an IP is whitelisted, so remove the whitelisted IP if you need rotating in that country. Static is not affected.
- Rotation happens per connection, not per request. If your client keeps the connection alive it will stay on one IP, so open a fresh connection per request (or send a Connection: close header) when you want a new IP each time.

#### Renewing

Yes. Check the price first with GET /orders/:id/extend-options, which lists the packages that qualify and the exact total for each. Then POST /purchase/extend with the orderId and the packageId of a Datacenter package with the same number of IPs and the same bandwidth as your plan; only the duration may differ. Renewing resets your data to the plan's full GB for the new term. Any data you haven't used yet doesn't carry over. Your remaining days do: the new term is added on top of your current end date. Add-ons you bought with the plan (high_concurrency, high_priority, extra whitelist slots) are charged again at +25% each. Read GET /orders/:id afterwards for the new end date. See Renewing a Datacenter or IPv6 plan below.

**Renewing a Datacenter or IPv6 plan**

Three calls: check the price, renew, then read the plan again for the new end date. The price check charges nothing, so call it as often as you like.

```bash
# 1. Check the price. Charges nothing.
#    renewable must be true. Pick one of options[] and note its package_id
#    and total, and show note to whoever is paying.
curl "https://catproxies.com/api/v1/user/orders/ORDER_ID/extend-options" \
  -H "Authorization: Bearer cp_your_api_key_here"

# 2. Renew, paid from your balance (it must cover options[].total).
curl -X POST "https://catproxies.com/api/v1/user/purchase/extend" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "orderId": "ORDER_ID", "packageId": "PACKAGE_ID_FROM_STEP_1", "paymentMethod": "Balance" }'
# -> payload.ExtendOrderId, payload.extendOrder.amount (what was charged)

# 3. Read the plan again: proxy.expired_at is the new end date,
#    proxy.bandwidth_left the data you now have.
curl "https://catproxies.com/api/v1/user/orders/ORDER_ID" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

- Renew before the end date. An expired plan cannot be renewed; buy a new one instead.
- Datacenter: renewing resets your data to the plan's full GB for the new term. Any data you haven't used yet doesn't carry over. Your remaining days do: the new term is added on top of your current end date. Only packages with the same number of IPs and the same bandwidth qualify, and add-ons are charged again at +25% each.
- If step 2 times out or you are not sure it landed, do step 3 before trying again. A renewal that went through has moved proxy.expired_at (and on an IPv6 bandwidth plan raised the data). Sending it again would renew, and charge, a second time.
- Two 500 messages mean opposite things. "Failed to extend proxy plan. Your balance has been refunded." means nothing happened: try again later. "Your renewal payment went through, but we need to confirm it with a quick manual check..." means you WERE charged and our team finishes it within 24 hours or refunds it: do not retry.
- One renewal per plan at a time: a second request while one is running gets a 409 and is not charged. Renewals share the 3-per-minute limit with purchases and password resets.
- Related: POST /orders/:id/reset-password rotates the password on both products, and GET /orders/:id/stats shows how the plan is being used.

#### Usage stats

GET /orders/:id/stats gives traffic and requests over time, a log of recent connections and your top sites. See Usage stats below.

#### Good to know

- **Where these hosts come from:** These gateways are fixed, and the same hosts are on the plan page in your dashboard. If we ever move a gateway the hostname stays the same, so build your proxy strings from the names above rather than resolving one to an IP and hard-coding that.
- **United States gateway:** Your US IPs sit behind two addresses, each covering a part of your IPs, and dcus.catproxies.com holds all of them (Canada and Germany work the same way with dcca and dcde). Your machine picks one of them and keeps it for about a minute before it can switch, so within one minute you rotate through part of your IPs and over a few minutes through all of them.
- **Worldwide gateway:** dcww.catproxies.com only works on a plan that owns IPs in all five countries (US, CA, DE, GB and NL). On a smaller plan it answers 407 whenever it lands on a country you did not buy, so use the per-country gateways instead.
- **country_proxies:** Must total exactly the package's IP count. Allowed countries: US, CA, DE, GB and NL. Over-allocating a country fails the purchase, so read GET /packages/datacenterp-countries first.
- **Add-ons:** high_concurrency (more parallel connections), high_priority (priority routing) and whitelisted_ips (3 whitelist slots instead of 1). Each one adds 25% to the price, so all three is +75%. All four keys must be present in datacenterPData.
- **Bandwidth:** One shared GB pool across every IP in the plan. Track it with proxy.bandwidth_left on GET /orders/:id.
- **Errors from the proxy:** 407 means wrong credentials or an address that is not whitelisted. 429 means you hit the plan's concurrent-connection limit, so lower your parallelism or buy with high_concurrency.
- **Rotate the password:** POST /orders/:id/reset-password issues one new password shared by every IP. Swap it into all of your proxy strings.

### IPv6 (`Ipv6p`)

*The cheapest way to make a very large number of requests.*

#### What it is

Rotates through a very large pool of IPv6 addresses, with a fresh exit on every request by default. Two flavours in the catalogue: bandwidth plans with a GB pool and no speed cap, and unlimited plans with no GB cap and a speed cap in Mbps. Buying needs nothing but the packageId.

#### Buying

```bash
curl -X POST https://catproxies.com/api/v1/user/purchase \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "PACKAGE_ID", "paymentMethod": "Balance" }'
```

#### Proxy format and connecting

GET /orders/:id gives you proxy.username and proxy.password. The exit country is decided by which gateway you connect to, and the same credentials work on all of them.

| Gateway | HTTP port | SOCKS5 port |
| --- | --- | --- |
| ipv6-ww.catproxies.com (worldwide) | 1338 | 1339 |
| ipv6-us.catproxies.com | 1338 | 1339 |
| ipv6-de.catproxies.com | 1338 | 1339 |
| ipv6-gb.catproxies.com | 1338 | 1339 |
| ipv6-nl.catproxies.com | 1338 | 1339 |

**Example**

```bash
# From GET /orders/:id
#   username: ipv6user291   password: v6pass817

# Rotating, a fresh IPv6 address on every request (US gateway)
curl -x ipv6-us.catproxies.com:1338 -U "ipv6user291:v6pass817" https://api6.ipify.org

# Sticky, hold the same exit for 60 minutes (ttl is in SECONDS, so 3600)
curl -x ipv6-de.catproxies.com:1338 \
  -U "ipv6user291-session-483920-ttl-3600:v6pass817" \
  https://api6.ipify.org

# SOCKS5
curl --socks5 ipv6-us.catproxies.com:1339 -U "ipv6user291:v6pass817" https://api6.ipify.org
```

#### Targeting, appended to the username

The country comes from the gateway you pick. The only flags are the ones that hold an IP still.

| Target | Flag | Value format |
| --- | --- | --- |
| Sticky session | `-session-483920-ttl-3600` | session id is 6 digits; ttl is in SECONDS, up to 86400 (24h) |

- Without the session flags you get a new IPv6 address on every request.
- The session id is a 6-digit number, 000000 to 999999. Pick your own and reuse it to keep the same exit, or generate a fresh one per line.
- ttl is in seconds, not minutes: 1800 is 30 minutes and 3600 is one hour. The maximum is 86400, which is 24 hours.
- Use different session ids to hold several sticky IPs at the same time.

#### Renewing

Yes. Check the price first with GET /orders/:id/extend-options, which lists the packages that qualify and the exact total for each. Then POST /purchase/extend with the orderId and the packageId of a plan of the same kind. An unlimited plan renews with the unlimited package of the same speed and the same length (a 30-day plan renews for 30 days), added on top of your current end date. A bandwidth plan renews with any IPv6 bandwidth package that keeps it at or under 5000 GB in total (the most an IPv6 plan can hold): its GB are added to what you have left, and the end date moves forward by your plan's own term (the number of days it was bought with). Read GET /orders/:id afterwards for the new end date and bandwidth. See Renewing a Datacenter or IPv6 plan below.

**Renewing a Datacenter or IPv6 plan**

Three calls: check the price, renew, then read the plan again for the new end date. The price check charges nothing, so call it as often as you like.

```bash
# 1. Check the price. Charges nothing.
#    renewable must be true. Pick one of options[] and note its package_id
#    and total, and show note to whoever is paying.
curl "https://catproxies.com/api/v1/user/orders/ORDER_ID/extend-options" \
  -H "Authorization: Bearer cp_your_api_key_here"

# 2. Renew, paid from your balance (it must cover options[].total).
curl -X POST "https://catproxies.com/api/v1/user/purchase/extend" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "orderId": "ORDER_ID", "packageId": "PACKAGE_ID_FROM_STEP_1", "paymentMethod": "Balance" }'
# -> payload.ExtendOrderId, payload.extendOrder.amount (what was charged)

# 3. Read the plan again: proxy.expired_at is the new end date,
#    proxy.bandwidth_left the data you now have.
curl "https://catproxies.com/api/v1/user/orders/ORDER_ID" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

- Renew before the end date. An expired plan cannot be renewed; buy a new one instead.
- IPv6 unlimited plan: the package's days are added on top of your current end date. IPv6 bandwidth plan: the package's GB are added on top of the data you have left, and the end date moves forward by the plan's own term (the number of days it was bought with, which is the package's days when you renew with the plan's own package).
- If step 2 times out or you are not sure it landed, do step 3 before trying again. A renewal that went through has moved proxy.expired_at (and on an IPv6 bandwidth plan raised the data). Sending it again would renew, and charge, a second time.
- Two 500 messages mean opposite things. "Failed to extend proxy plan. Your balance has been refunded." means nothing happened: try again later. "Your renewal payment went through, but we need to confirm it with a quick manual check..." means you WERE charged and our team finishes it within 24 hours or refunds it: do not retry.
- One renewal per plan at a time: a second request while one is running gets a 409 and is not charged. Renewals share the 3-per-minute limit with purchases and password resets.
- Related: POST /orders/:id/reset-password rotates the password on both products, and GET /orders/:id/stats shows how the plan is being used.

#### Usage stats

GET /orders/:id/stats gives traffic and requests over time, a log of recent connections and your top sites. See Usage stats below.

#### Good to know

- **Where these hosts come from:** These gateways are fixed, and the same list is on the plan page in your dashboard, which is always current. If we ever move a gateway the hostname stays the same, so build your proxy strings from the names above rather than resolving one to an IP and hard-coding that.
- **Your target site must support IPv6:** Exits are IPv6 only, so the destination needs an AAAA DNS record. Google, YouTube, Facebook, Instagram and TikTok all do; many smaller sites do not and will simply be unreachable. Test your target first, then confirm the proxy end to end against https://api6.ipify.org. A site with no IPv6 usually shows up as a 503 from the proxy.
- **No IP whitelist:** IPv6 plans are username and password only. There is no whitelist on this product.
- **Bandwidth:** On bandwidth plans, proxy.bandwidth_left on GET /orders/:id is what is left. On unlimited plans there is no GB figure; the plan is capped by speed instead.
- **Rotate the password:** POST /orders/:id/reset-password issues a new password. The session flags live on the username, so only the password half of your proxy strings changes.

### Rotating Mobile (`RotatingMobile`)

*Real mobile carrier IPs, sold by the gigabyte.*

#### What it is

Sold by bandwidth and bought with nothing but the packageId. One set of credentials covers every location; you choose the location with flags on the username. A fresh IP on every request by default.

#### Buying

```bash
curl -X POST https://catproxies.com/api/v1/user/purchase \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "PACKAGE_ID", "paymentMethod": "Balance" }'
```

#### Proxy format and connecting

GET /orders/:id gives you proxy.username and proxy.password. The host is one of the three fixed regional gateways below: pick the one nearest your target country, since crossing continents costs real latency. The same credentials work on all of them, and one port serves every location. Ignore proxy.server_ip on this product; it is not set.

| Gateway | Port | Protocol |
| --- | --- | --- |
| mobile-us.catproxies.com (Americas) | 5000 | HTTP and HTTPS |
| mobile-eu.catproxies.com (Europe) | 5000 | HTTP and HTTPS |
| mobile-asia.catproxies.com (Asia and Oceania) | 5000 | HTTP and HTTPS |

**Example**

```bash
# From GET /orders/:id
#   username: mob8814720   password: r4Xq9tLp
# Host: pick the gateway nearest your target from the table above

# Untargeted rotation
curl -x mobile-us.catproxies.com:5000 -U "mob8814720:r4Xq9tLp" https://ipinfo.io

# United States, a specific carrier, pinned at country level
curl -x mobile-us.catproxies.com:5000 \
  -U "mob8814720-country-US-isp-at&t+wireless:r4Xq9tLp" \
  https://ipinfo.io

# Hold one IP for 30 minutes
curl -x mobile-us.catproxies.com:5000 \
  -U "mob8814720-country-US-sid-ab12-ttl-30m:r4Xq9tLp" \
  https://ipinfo.io
```

#### Targeting, appended to the username

Leave the password alone and append flags to the username. Every flag is optional; with none of them you get untargeted rotation.

| Target | Flag | Value format |
| --- | --- | --- |
| Country | `-country-US` | ISO-2 code |
| Region or state | `-region-california` | full name in lowercase, from /generator/mobile-locations/regions |
| City | `-city-los+angeles` | from /generator/mobile-locations/cities. Spaces are a plus sign. |
| Carrier | `-isp-at&t+wireless` | from /generator/mobile-locations/isps |
| Sticky session | `-sid-ab12-ttl-30m` | any id, plus one of 1m 5m 10m 30m 60m 120m 180m 240m |

- Write spaces as a plus sign, never a hyphen: los+angeles, new+york, at&t+wireless. Regions are full names such as california, not two-letter codes.
- An unknown or malformed value fails outright rather than quietly falling back to untargeted rotation. If a targeted request errors, check the code against the targeting endpoints first.
- Carrier coverage varies by city, so a valid city and carrier pair can still fail if that carrier has no live IP there right now. For dependable carrier targeting, pin the carrier at country level and leave the city off. City targeting is approximate to the metro area, not pinpoint.
- Untargeted rotation is not evenly spread across carriers. If you need a particular mix, ask for each carrier explicitly.

#### Renewing

Yes. POST /purchase/extend with the orderId and the packageId of the size you want.

#### Good to know

- **Where these hosts come from:** These gateways are fixed, and the same list is on the plan page in your dashboard, which is always current. If we ever move a gateway the hostname stays the same, so build your proxy strings from the names above rather than resolving one to an IP and hard-coding that.
- **Bandwidth:** proxy.bandwidth_left on GET /orders/:id is what you have left, in GB.
- **Reset the login:** POST /orders/:id/reset-password gives the plan a new login and moves your remaining data onto it. The username AND the password both change, so rebuild your proxy strings from the username and password in the response. The old login stops working straight away. Once per hour per plan.
- **Cache the targeting lists:** The four /generator/mobile-locations/* endpoints are cached on our side for an hour and the lists change rarely, so fetch them once at startup rather than per request. They still count against your hourly budget.
- **Sticky windows:** ttl accepts only the listed values, up to 240m (4 hours). Anything else is rejected.

### Usage stats

Datacenter and IPv6 plans come with usage analytics: traffic and requests over time, a log of recent connections, and the sites used most. It is one read-only endpoint, GET /orders/:id/stats, and the view parameter picks which of the three you get. It never changes the plan.

```bash
# Traffic over the last 24 hours, one row per hour (the defaults)
curl "https://catproxies.com/api/v1/user/orders/ORDER_ID/stats" \
  -H "Authorization: Bearer cp_your_api_key_here"

# The last week, one row per day
curl "https://catproxies.com/api/v1/user/orders/ORDER_ID/stats?timeframe=1week&interval=1day" \
  -H "Authorization: Bearer cp_your_api_key_here"

# Recent connections, 100 per page
curl "https://catproxies.com/api/v1/user/orders/ORDER_ID/stats?view=activity&page=1" \
  -H "Authorization: Bearer cp_your_api_key_here"

# The sites used most this month
curl "https://catproxies.com/api/v1/user/orders/ORDER_ID/stats?view=top-domains&timeframe=1month" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

- timeframe: 1hour, 6hours, 1day (default), 1week, 1month or 3months. interval, for the overview only: 5min, 30min, 1hour (default), 6hour, 1day, 1week or 1month. An unknown value quietly falls back to the default, so check timeframe and interval in the reply.
- available: false means the stats source could not be reached just now and the lists are empty. Retry later; it does not mean zero traffic.
- The figures lag a little behind real time. For what is left on the plan right now, read proxy.bandwidth_left on GET /orders/:id instead.
- Units: the overview is in GB, activity and top-domains traffic is in bytes, and success_rate is a percentage. Timestamps are "YYYY-MM-DD HH:MM:SS" strings.
- GET /orders/:id/unlimited-metrics is the older name of the same endpoint and returns exactly the same thing.

## Endpoints

All paths are relative to `https://catproxies.com/api/v1/user`. Responses are JSON shaped as `{ type, message, payload, pagination? }`. Check `type` as well as the HTTP status, because some handled failures answer 200 with `type: "error"`.

### Account

#### `GET /account`

Everything a client normally needs about the account itself: the balance orders are paid from, how many plans you have, and your rate limit.

Request:

```bash
curl https://catproxies.com/api/v1/user/account \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "email": "you@example.com",
    "displayName": "Alex",
    "country": "Germany",
    "balance": 42.5,
    "currency": "USD",
    "pricing": "retail",
    "totalOrders": 12,
    "activeOrders": 3,
    "rateLimits": { "requestsPerHour": 1000 }
  }
}
```

#### `GET /balance`

Your deposit and spend history, newest first. Each row is one movement. Use /account for the current balance figure itself; this endpoint returns the ledger, not a running total.

Request:

```bash
curl https://catproxies.com/api/v1/user/balance \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "balances": [
      {
        "id": "b41e9d02-7c3a-4f21-9b88-2c5d1e6a0f34",
        "type": "Expense",
        "balance": 2.5,
        "currency": "USD",
        "status": "CONFIRMED",
        "paymentMethod": "Balance",
        "proxyType": "v2resi",
        "orderId": "3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904",
        "extendOrderId": null,
        "invoice_number": null,
        "createdAt": "2026-09-04T10:12:00.000Z",
        "order": {
          "id": "3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904",
          "package_title": "Standard Resi 1GB V2",
          "package_proxyType": "v2resi",
          "amount": 2.5
        }
      }
    ]
  },
  "pagination": { "page": 1, "pageSize": 10, "Total": 34, "Pages": 4 }
}
```

### Catalogue

#### `GET /packages`

The same catalogue, and the same retail prices, as the Purchase Plan page in your dashboard. Filter by product with `proxyType`. A package `id` from here is what you send to POST /purchase.

Custom ISP is the exception. With `proxyType=Isp`, the list starts with a marker entry `{ "id": "customisp", "title": "Custom ISP", "customIsp": true }` while Custom ISP is on sale. It is not a package and cannot be bought by that id. Custom ISP plans, prices and cities come from GET /custom-isp/catalog.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `proxyType` | string | no | Product to list: "v2resi", "resix", "Isp", "IspP", "DatacenterP", "Ipv6p" or "RotatingMobile". |
| `page` | number | no | Page number. Default 1. |
| `pageSize` | number | no | Results per page. Default 10. |
| `search` | string | no | Free-text filter on the plan title. |

Request:

```bash
curl "https://catproxies.com/api/v1/user/packages?proxyType=v2resi&pageSize=50" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "packages": [
      {
        "id": "cdd0c81b-43fd-4bef-9709-2173a5ad3c5f",
        "title": "Standard Resi 1GB V2",
        "description": "Standard Resi 1GB",
        "price": 2.5,
        "bandwidth": 1,
        "days": 90,
        "ips": null,
        "speed": 0,
        "proxyType": "v2resi",
        "hidden": false
      }
    ]
  },
  "pagination": { "page": 1, "pageSize": 50, "Total": 6, "Pages": 1 }
}
```

#### `GET /packages/:id`

The full record for a single package: price, bandwidth or IP count, duration and product type.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Package id from /packages. |

Request:

```bash
curl https://catproxies.com/api/v1/user/packages/cdd0c81b-43fd-4bef-9709-2173a5ad3c5f \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "package": {
      "id": "cdd0c81b-43fd-4bef-9709-2173a5ad3c5f",
      "title": "Standard Resi 1GB V2",
      "price": 2.5,
      "bandwidth": 1,
      "days": 90,
      "proxyType": "v2resi"
    }
  }
}
```

#### `GET /packages/datacenterp-countries`

How many Datacenter IPs are free right now in each country. Check this before you allocate, because asking for more than a country has will fail the purchase.

Request:

```bash
curl https://catproxies.com/api/v1/user/packages/datacenterp-countries \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": [
    { "name": "United States", "code": "US", "available": 4180 },
    { "name": "Germany", "code": "DE", "available": 970 }
  ]
}
```

#### `GET /custom-isp/catalog`

Everything you need to build a Custom ISP order. Custom ISP is static ISP proxies where you pick the tier, the plan length, the city and, if you want, the carrier.

plans lists one package per tier and length. Its packageId is what you send to POST /purchase. pricePerIp is the price you pay for one IP for that length. It already includes any launch discount and your personal price. countries lists the cities with IPs in stock right now for the tier and UDP choice you asked for, with how many IPs each has. Send a city's location string exactly as returned.

The two tiers: standard, and premium, which is shown as Clean Score on the website. Clean Score IPs are checked right before delivery and have a fraud score of 0 when you get them. Checking an IP on a score site through the proxy raises its score, so check it from a connection that is not using the proxy.

The IP counts come from a cache that is at most a minute old, so a city can run out between this call and your purchase. The purchase checks stock again and refuses cleanly if it did.

When Custom ISP is not on sale the payload is just `{ "enabled": false }`. When the tier you asked for has no plans, countries is empty. Limited to 40 requests per minute per account.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `tier` | string | no | "standard" (default) or "premium" (Clean Score). Picks which tier's cities and IP counts are listed. plans always holds every tier on sale. |
| `udp` | string | no | "1" to count only IPs with UDP support. Default "0" (every IP). |

Request:

```bash
curl "https://catproxies.com/api/v1/user/custom-isp/catalog?tier=standard&udp=0" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "enabled": true,
    "tiers": ["standard", "premium"],
    "plans": {
      "standard": {
        "1_day": { "packageId": "a1b2c3d4-0001-4a5b-8c9d-0e1f2a3b4c5d", "pricePerIp": 0.6, "listPricePerIp": 0.6, "promoPct": 0, "customRate": false, "days": 1, "minQty": 1, "maxQty": 100, "replacementPrice": 1.1 },
        "1_month": { "packageId": "a1b2c3d4-0003-4a5b-8c9d-0e1f2a3b4c5d", "pricePerIp": 3.6, "listPricePerIp": 4, "promoPct": 10, "customRate": false, "days": 30, "minQty": 1, "maxQty": 100, "replacementPrice": 1.1 }
      },
      "premium": {
        "1_month": { "packageId": "a1b2c3d4-0103-4a5b-8c9d-0e1f2a3b4c5d", "pricePerIp": 5, "listPricePerIp": 5, "promoPct": 0, "customRate": false, "days": 30, "minQty": 1, "maxQty": 100, "replacementPrice": 1.1 }
      }
    },
    "carrierFee": 0.35,
    "maxReplacements": 5,
    "countries": [
      {
        "country": "United States",
        "total": 1840,
        "cities": [
          { "city": "New York City", "location": "United States - New York City", "count": 412 },
          { "city": "Ashburn", "location": "United States - Ashburn", "count": 298 }
        ]
      }
    ]
  }
}
```

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `enabled` | boolean | false when Custom ISP is not on sale right now. Nothing else is sent then. |
| `tiers` | string[] | Tiers that have at least one plan: "standard" and/or "premium" (Clean Score). |
| `plans[tier][length]` | object | One plan per tier and length. Lengths: "1_day", "7_days", "1_month", "2_months", "3_months". Only lengths on sale are listed. |
| `plans[tier][length].packageId` | string | Send this as packageId to POST /purchase. |
| `plans[tier][length].pricePerIp` | number | What one IP costs you for this length, before VAT. Includes any launch discount and your personal price. |
| `plans[tier][length].listPricePerIp` | number | The normal price per IP, before any discount. |
| `plans[tier][length].promoPct` | number | Launch discount in percent, 0 when none. Coupons are refused while it is above 0. |
| `plans[tier][length].customRate` | boolean | true when your personal price applies. Coupons are refused then too. |
| `plans[tier][length].days` | number | Plan length in days. |
| `plans[tier][length].minQty, maxQty` | number | How many IPs one order can have. |
| `plans[tier][length].replacementPrice` | number \| null | Price of one IP replacement bought with this plan. null when replacements are not sold on it. |
| `carrierFee` | number | Extra price per IP when you pick a carrier (ispData.asn). |
| `maxReplacements` | number | Most IP replacements one order can be bought with (5). |
| `countries[].total` | number | IPs in stock in that country for this tier and UDP choice. |
| `countries[].cities[].location` | string | The city as "Country - City". Send it exactly like this as ispData.location and to GET /custom-isp/carriers. |
| `countries[].cities[].count` | number | IPs in stock in that city. |

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 400 | Live stock is unavailable right now. Please try again in a minute. | We could not read the stock just now. Nothing to fix on your side; try again shortly. |
| 429 | Too many requests. Please wait a moment. | More than 40 calls in a minute. Cache the result for a minute instead of polling. |

#### `GET /custom-isp/carriers`

The carriers (ASNs) with IPs in stock in one city, for one tier and UDP choice. Picking a carrier is optional. If you do, send its asn as ispData.asn on POST /purchase and every IP costs carrierFee more.

location must be a city exactly as GET /custom-isp/catalog returns it. Carriers are listed best first: tier 1 is a top consumer carrier, then ISP, backbone network and hosting network. The list is cached for about two minutes. Limited to 40 requests per minute per account.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `location` | string | yes | A city from the catalog, for example "United States - New York City". |
| `tier` | string | no | "standard" (default) or "premium". Use the same tier you will buy. |
| `udp` | string | no | "1" to count only IPs with UDP support. Use the same choice you will buy. |

Request:

```bash
curl -G "https://catproxies.com/api/v1/user/custom-isp/carriers" \
  --data-urlencode "location=United States - New York City" \
  --data-urlencode "tier=standard" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "carrierFee": 0.35,
    "tierLabels": { "1": "Top carrier", "2": "ISP", "3": "Backbone network", "4": "Hosting network" },
    "carriers": [
      { "asn": 7922, "name": "Comcast Cable Communications", "stock": 96, "tier": 1 },
      { "asn": 701, "name": "Verizon Business", "stock": 41, "tier": 1 }
    ]
  }
}
```

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `carrierFee` | number | Extra price per IP when you pick one of these carriers. |
| `tierLabels` | object | Names for the carrier tiers 1 to 4. |
| `carriers[].asn` | number | Send this as ispData.asn on POST /purchase. |
| `carriers[].name` | string | The carrier's name. |
| `carriers[].stock` | number | IPs on that carrier in stock in this city. |
| `carriers[].tier` | number | 1 is best. See tierLabels. |

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 400 | This city is not available. | location is not a city from the catalog, or it has no IPs in stock for this tier and UDP choice. |
| 400 | Carrier stock is unavailable right now. Please try again in a minute. | We could not read the stock just now. Try again shortly. |
| 400 | validation error | location is missing or longer than 120 characters. |
| 400 | This product is not available right now. | Custom ISP is not on sale at the moment. |
| 429 | Too many requests. Please wait a moment. | More than 40 calls in a minute. |

### Buying and renewing

#### `POST /purchase`

Creates the order, charges your account balance and sets the plan up. The response contains the new order; read its credentials with GET /orders/:id. `paymentMethod` must be "Balance": an API order cannot open a card checkout, so top up on the website first if the balance is short.

If the plan could not be set up on the spot, the response can come back with `payload.settingUp: true` and `order.status: "PENDING"`. You have been charged and the plan is being set up automatically, which usually takes a few minutes. Do not buy again: keep reading GET /orders/:id until it shows the plan's credentials. Plans still being set up are also listed in `payload.settingUp` on GET /orders.

Custom ISP: take packageId from GET /custom-isp/catalog (plans[tier][length].packageId) and send your picks in ispData: quantity and location are required, asn, udp and replacements are optional. The price is worked out on our side: price per IP x IPs, plus the carrier fee per IP x IPs when you set asn, plus replacements x the plan's replacement price. VAT is added on top as for any purchase. Live stock is checked before you are charged. A Custom ISP plan can also answer with settingUp while its IPs are placed: read GET /generator/isp a little later for the lines.

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `packageId` | string | yes | The package to buy, from /packages. |
| `paymentMethod` | string | yes | Always "Balance" for API orders. |
| `ispData` | object | no | ISP products only. Dedicated ISP: { quantity }. Custom ISP: { quantity, location, asn?, udp?, replacements?, price? }, see the ispData rows below. |
| `ispData.quantity` | integer | no | Custom ISP: number of IPs, within the plan's minQty and maxQty from the catalog. |
| `ispData.location` | string | no | Custom ISP: the city, exactly as GET /custom-isp/catalog returns it ("Country - City"), for example "United States - New York City". |
| `ispData.asn` | number | no | Custom ISP, optional: a carrier from GET /custom-isp/carriers for that city. Adds the carrier fee per IP. Leave it out for any carrier. |
| `ispData.udp` | boolean | no | Custom ISP, optional: true gives only IPs with UDP support. Default false. |
| `ispData.replacements` | integer | no | Custom ISP, optional: IP replacements bought with the plan, 0 to 5, each at the plan's replacementPrice. Default 0. They are used with POST /custom-isp/orders/:id/replace and are not renewed. IP replacements can only be bought together with the order: they can't be added later, so choose how many you need when you buy. |
| `ispData.price` | number | no | Custom ISP, optional: the total before VAT you expect to pay. If you send it, it must match our total within 0.02 or the order is refused. Our own total is always what is charged. |
| `datacenterPData` | object | no | Datacenter only. { country_proxies: { US: 60, DE: 20 }, high_concurrency, high_priority, whitelisted_ips }. All four keys are required. |

Request:

```bash
# Any bandwidth product: just the packageId
curl -X POST https://catproxies.com/api/v1/user/purchase \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "packageId": "cdd0c81b-43fd-4bef-9709-2173a5ad3c5f",
    "paymentMethod": "Balance"
  }'

# Custom ISP: 5 IPs in New York City on one carrier, with 2 IP replacements
curl -X POST https://catproxies.com/api/v1/user/purchase \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "packageId": "a1b2c3d4-0003-4a5b-8c9d-0e1f2a3b4c5d",
    "paymentMethod": "Balance",
    "ispData": {
      "quantity": 5,
      "location": "United States - New York City",
      "asn": 7922,
      "udp": false,
      "replacements": 2
    }
  }'
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "PaymentMethod": "Balance",
    "PaymentPayload": { "hosted_url": "https://catproxies.com/dashboard/my-plans/active-plans" },
    "OrderId": "3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904",
    "order": {
      "id": "3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904",
      "status": "CONFIRMED",
      "package_title": "Standard Resi 1GB V2",
      "package_proxyType": "v2resi",
      "amount": 2.5,
      "expire": "2026-12-03T12:00:00.000Z"
    }
  }
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 400 | ISP Region and proxy amount are required | Custom ISP: ispData.quantity or ispData.location is missing. |
| 400 | Quantity must be between 1 and 100. | Custom ISP: the quantity is outside the plan's minQty and maxQty. |
| 400 | This city is out of stock right now. Please pick another one. | Custom ISP: the city ran out, or location is not a city from the catalog. Nothing was charged. |
| 400 | This carrier is out of stock in this city right now. Please pick another one. | Custom ISP: no IPs left on that asn in that city. Nothing was charged. |
| 400 | Only 3 IPs are available for this choice right now. Please lower the quantity or change your filters. | Custom ISP: fewer IPs in stock than you asked for. Nothing was charged. |
| 400 | You can add up to 5 IP replacements. | Custom ISP: replacements is above 5 or below 0. |
| 400 | Invalid ISP price | Custom ISP: the price you sent differs from our total by more than 0.02. Read the catalog again; prices or your discount may have changed. |
| 400 | Insufficient balance | Top up and try again. Nothing was charged. |
| 400 | ISP packages are currently unavailable. | Custom ISP is switched off right now. Nothing was charged. |
| 409 | This product is not available at the moment. Please try again later. | New purchases of this product or plan are paused. Nothing was charged. |

#### `GET /orders/:id/extend-options`

A price check before you renew a Datacenter or IPv6 plan. It lists every package this plan can be renewed with, and exactly what POST /purchase/extend would take from your balance for each one. It charges nothing and changes nothing.

Which packages are listed:
• Datacenter: packages with the same number of IPs and the same bandwidth as the plan. Only the duration differs. The plan's own package is always included.
• IPv6 unlimited plan: the unlimited package with the same speed and the same length as the plan.
• IPv6 bandwidth plan: every IPv6 bandwidth package that keeps the plan at or under 5000 GB in total (the most an IPv6 plan can hold).
The plan's current package comes first (is_current_package: true), then the rest, shortest first.

note says what the renewal does to the plan's data and days, in the same words as the renew window on the website. On a Datacenter plan with a data cap it reads: "Renewing resets your data to 250 GB for the new term. Any data you haven't used yet doesn't carry over. Your remaining days do: the new term is added on top of your current end date." (250 is the plan's own GB.) Show it to whoever is paying.

price is before VAT: the package price, times the add-on multiplier on Datacenter (+25% for each add-on the plan was bought with). total is what the renewal takes from your balance, with VAT and any balance fee included. Coupons are not applied here, so a valid coupon on the renewal lowers the real charge.

When renewable is false, options is empty and reason says why, in the same words the renewal itself would use. renewable: true is not a promise: the renewal can still be refused for things this check does not look at, such as your balance, your profile details or account restrictions.

Custom ISP and Dedicated ISP are not covered here and answer 400. Use GET /purchase/isp-extend-price for those.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Order id of a Datacenter or IPv6 plan. |

Request:

```bash
curl https://catproxies.com/api/v1/user/orders/c5c7c6b5-e488-420c-b436-e90e86031751/extend-options \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
// Datacenter plan (100 IPs, 250 GB, bought with one add-on, account VAT 21%)
{
  "type": "response",
  "message": "Success",
  "payload": {
    "order_id": "c5c7c6b5-e488-420c-b436-e90e86031751",
    "product": "Datacenter",
    "renewable": true,
    "reason": null,
    "expires_at": "2026-10-11T10:00:00.000Z",
    "renews_by": "days",
    "note": "Renewing resets your data to 250 GB for the new term. Any data you haven't used yet doesn't carry over. Your remaining days do: the new term is added on top of your current end date.",
    "addons": {
      "high_concurrency": true,
      "high_priority": false,
      "extra_whitelist_slots": false,
      "count": 1,
      "price_multiplier": 1.25
    },
    "vat_rate": 0.21,
    "currency": "USD",
    "options": [
      {
        "package_id": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
        "title": "Datacenter Proxies - 1 Month - 100 IPs - 250GB",
        "is_current_package": true,
        "days": 30,
        "bandwidth_gb": 250,
        "ips": 100,
        "speed": null,
        "adds": "30 days",
        "price": 125,
        "vat": 26.25,
        "fee": 0,
        "total": 151.25
      },
      {
        "package_id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
        "title": "Datacenter Proxies - 3 Months - 100 IPs - 250GB",
        "is_current_package": false,
        "days": 90,
        "bandwidth_gb": 250,
        "ips": 100,
        "speed": null,
        "adds": "90 days",
        "price": 300,
        "vat": 63,
        "fee": 0,
        "total": 363
      }
    ]
  },
  "pagination": {}
}

// IPv6 unlimited plan (100 Mbps, no VAT on this account). There is no addons key on IPv6
{
  "type": "response",
  "message": "Success",
  "payload": {
    "order_id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "product": "IPv6",
    "renewable": true,
    "reason": null,
    "expires_at": "2026-10-02T08:30:00.000Z",
    "renews_by": "days",
    "note": "Renewing adds the package's days on top of your current end date.",
    "vat_rate": 0,
    "currency": "USD",
    "options": [
      {
        "package_id": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
        "title": "IPv6 Unlimited 100 Mbps 30 Days",
        "is_current_package": true,
        "days": 30,
        "bandwidth_gb": null,
        "ips": null,
        "speed": 100,
        "adds": "30 days",
        "price": 25,
        "vat": 0,
        "fee": 0,
        "total": 25
      }
    ]
  },
  "pagination": {}
}

// A plan that cannot be renewed
{
  "type": "response",
  "message": "Success",
  "payload": {
    "order_id": "c5c7c6b5-e488-420c-b436-e90e86031751",
    "product": "Datacenter",
    "renewable": false,
    "reason": "Cannot extend an expired proxy",
    "expires_at": "2026-09-01T10:00:00.000Z",
    "renews_by": "days",
    "note": "Renewing resets your data to 250 GB for the new term. Any data you haven't used yet doesn't carry over. Your remaining days do: the new term is added on top of your current end date.",
    "addons": { "high_concurrency": false, "high_priority": false, "extra_whitelist_slots": false, "count": 0, "price_multiplier": 1 },
    "vat_rate": null,
    "currency": "USD",
    "options": []
  },
  "pagination": {}
}
```

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `product` | string | "Datacenter" or "IPv6". |
| `renewable` | boolean | false means POST /purchase/extend will refuse this plan right now. reason says why. |
| `reason` | string \| null | Why the plan cannot be renewed, or null. The same message the renewal would give. |
| `expires_at` | string \| null | The plan's current end date (ISO 8601). |
| `renews_by` | string | "days" (Datacenter, IPv6 unlimited plans: the renewal adds time) or "bandwidth" (IPv6 bandwidth plans: the renewal adds GB). |
| `note` | string \| null | What a renewal does to the plan's data and days, in the same words as the renew window on the website. Worth showing to whoever pays. On a Datacenter plan with a data cap it warns that the data resets and unused data does not carry over; null on a Datacenter plan with unlimited bandwidth. Always set on IPv6: on an unlimited plan it says the package's days are added to your end date, on a bandwidth plan that its GB are added to what you have left and the end date moves forward by your plan's own term. |
| `addons` | object | Datacenter only. Which add-ons the plan was bought with (high_concurrency, high_priority, extra_whitelist_slots), how many (count) and the price multiplier they give (1 + 0.25 per add-on). |
| `vat_rate` | number \| null | Your account's VAT rate as a fraction (0.21 = 21%). null when the plan is not renewable. |
| `options[].package_id` | string | Send this as packageId to POST /purchase/extend. |
| `options[].is_current_package` | boolean | true for the package the plan was bought with. |
| `options[].days` | number | Length of that package in days. |
| `options[].bandwidth_gb` | number \| null | GB in that package. null on unlimited IPv6 packages. |
| `options[].ips` | number \| null | Datacenter only: IP count (always the same as the plan's). |
| `options[].speed` | number \| null | IPv6 unlimited only: speed cap in Mbps (always the same as the plan's). |
| `options[].adds` | string | What the renewal adds, in words: "1 day", "30 days" or "50 GB". |
| `options[].price` | number | Price before VAT, add-ons included. |
| `options[].vat` | number | VAT on top of price. |
| `options[].fee` | number | Any fee for paying from balance (0 when none applies). |
| `options[].total` | number | What POST /purchase/extend takes from your balance for this package, before any coupon. |

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 404 | Order not found | No plan with that id on your account. |
| 400 | Renewal options are listed for Datacenter and IPv6 plans. For ISP plans use GET /api/v1/user/purchase/isp-extend-price. | The plan is another product. Other metered products renew with any package from /packages; ISP has its own price check. |
| 500 | Internal server error | Our side failed. Nothing was charged; safe to retry. |

#### `POST /purchase/extend`

Renews a plan you already own, paid from your balance. What you get is always worked out from the package on our side and never from the request, so send the order and, where the product offers a choice of renewal size, the package you want. Renew BEFORE the plan expires: an expired plan cannot be renewed back to life, and on ISP the IPs are released when it lapses, so you get different ones next time.

What a renewal adds depends on the product:
• Standard Residential, Premium Residential, Rotating Mobile: the bandwidth of the package you chose is ADDED to what is left, and the expiry date is pushed out by that package's term (on Standard Residential the end date becomes today plus the package's days).
• Custom ISP: the same plan again (same length, same IPs) at its per-IP price, plus the carrier fee per IP again when the plan was bought on a carrier. IP replacements are not renewed. Leave packageId out: the plan's own package is always used.
• Dedicated ISP: another full period at its own per-IP price, keeping the same IPs. Leave packageId out.
• Datacenter: packageId is required, and must be a Datacenter package with the same number of IPs and the same bandwidth as the plan. Only the duration may differ, and the plan's own package always qualifies. Renewing resets your data to the plan's full GB for the new term. Any data you haven't used yet doesn't carry over. Your remaining days do: the new term is added on top of your current end date. Add-ons the plan was bought with are charged again at +25% each.
• IPv6: packageId is required. An unlimited (speed) plan renews only with an unlimited IPv6 package of the same speed and the same length as the plan (a 30-day plan renews for 30 days), and those days are added on top of the current end date. A bandwidth plan renews with any IPv6 bandwidth package that keeps it at or under 5000 GB in total (the most an IPv6 plan can hold): its GB are added to what is left, and the end date moves forward by the plan's own term (the number of days it was bought with). The plan's own package qualifies while it is still on sale.

For Datacenter and IPv6, GET /orders/:id/extend-options lists the packages that qualify and the exact total for each. Take packageId from there.

VAT is added on top at your account's rate, exactly as on the website. A coupon, if you send one, comes off before VAT.

The response confirms the charge but does not carry the new end date or bandwidth. Read GET /orders/:id afterwards for those.

Datacenter, IPv6 and Custom ISP: only one renewal per plan can run at a time. A second request while the first is still running gets a 409 and is not charged.

Custom ISP and Dedicated ISP: check the cost first with GET /purchase/isp-extend-price.

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | The plan to renew. |
| `paymentMethod` | string | yes | Always "Balance" for API renewals. |
| `packageId` | string | no | Which renewal package to buy. Required for Standard Residential, Premium Residential, Rotating Mobile, Datacenter and IPv6. For Datacenter and IPv6, take it from GET /orders/:id/extend-options. Leave it out for Custom ISP and Dedicated ISP, which renew at their own per-IP price. |
| `coupon` | string | no | Optional coupon code. Comes off the price before VAT. An unknown, expired or used-up code is refused and nothing is charged. |
| `type, value, amount` | any | no | Ignored. Older clients send them; the charge and what is added always come from the package. |

Request:

```bash
# Datacenter or IPv6: packageId from GET /orders/:id/extend-options
curl -X POST https://catproxies.com/api/v1/user/purchase/extend \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "c5c7c6b5-e488-420c-b436-e90e86031751",
    "packageId": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
    "paymentMethod": "Balance"
  }'

# Residential or Mobile: packageId of the size you want, from /packages
curl -X POST https://catproxies.com/api/v1/user/purchase/extend \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904",
    "packageId": "cdd0c81b-43fd-4bef-9709-2173a5ad3c5f",
    "paymentMethod": "Balance"
  }'

# Custom ISP or Dedicated ISP: no packageId
curl -X POST https://catproxies.com/api/v1/user/purchase/extend \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "orderId": "8a08caa7-e1f2-487e-aedb-9ebc018ab8f0", "paymentMethod": "Balance" }'
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "PaymentMethod": "Balance",
    "PaymentPayload": { "hosted_url": "https://catproxies.com/dashboard/my-plans/active-plans" },
    "ExtendOrderId": "77aa1e08-6c52-4b6f-a0d1-9f3c2e5b8a17",
    "OrderId": "c5c7c6b5-e488-420c-b436-e90e86031751",
    "extendOrder": {
      "id": "77aa1e08-6c52-4b6f-a0d1-9f3c2e5b8a17",
      "orderId": "c5c7c6b5-e488-420c-b436-e90e86031751",
      "status": "CONFIRMED",
      "amount": 151.25
    },
    "paymentUrl": "https://catproxies.com/dashboard/my-plans/active-plans"
  }
}
```

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `ExtendOrderId` | string | Id of this renewal. Keep it as your reference. |
| `OrderId` | string | The plan you renewed. |
| `extendOrder.status` | string | "CONFIRMED": the renewal is paid and applied. |
| `extendOrder.amount` | number | What was taken from your balance, VAT and fees included. |
| `PaymentPayload.hosted_url, paymentUrl` | string | A link to the plans page in the dashboard. Nothing to do in code, ignore them. |

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 400 | Package not found | packageId is missing, unknown or no longer on sale. Datacenter and IPv6 need one: take it from GET /orders/:id/extend-options. |
| 400 | You can only extend with a Datacenter plan of the same size (same number of IPs and bandwidth). Only the duration can differ. | Datacenter: the package has a different IP count or bandwidth. Nothing was charged. |
| 400 | You can only renew this IPv6 plan with the same plan: same speed and the same length (30 days). | IPv6 unlimited plan: the package is a bandwidth package, a different speed or a different length. The number in brackets is your plan's own length. Nothing was charged. |
| 400 | You can only renew this IPv6 plan with an IPv6 bandwidth plan. | IPv6 bandwidth plan: the package is an unlimited one. Nothing was charged. |
| 400 | An IPv6 plan can hold at most 5000 GB. This package would take yours to 5250 GB, so please pick a smaller one. | IPv6 bandwidth plan: the plan plus the package would pass 5000 GB in total. Nothing was charged. Pick a smaller package from GET /orders/:id/extend-options. |
| 400 | Package proxy type does not match order proxy type | The package belongs to a different product than the plan. |
| 400 | Cannot extend an expired proxy | The plan has already run out. Buy a new plan instead. |
| 400 | Cannot extend a deleted proxy plan | The plan no longer exists on the network. Buy a new plan instead. |
| 400 | Insufficient balance | Top up and try again. Nothing was charged. |
| 400 | API renewals are paid from your account balance. Send paymentMethod "Balance" and top up first if needed. | You sent a paymentMethod other than "Balance". |
| 403 | Unauthorized access to this order | The plan belongs to another account. |
| 404 | Order not found | No plan with that id. |
| 409 | A renewal for this plan is already in progress. Please wait a moment for it to finish before trying again. | Datacenter, IPv6 and Custom ISP. A second renewal arrived while the first was still running. It was not charged. Wait, then check GET /orders/:id before deciding to renew again. |
| 409 | This plan's last renewal is being checked by our team. Please don't renew it again yet, contact us and we'll sort it out. | Datacenter, IPv6 and Custom ISP. An earlier renewal is under manual review. Nothing was charged for this request. Contact support. |
| 429 | Too many requests, Please try again after a minute | The shared 3-per-minute limit. Nothing was charged. |
| 500 | Failed to extend proxy plan. Your balance has been refunded. | The renewal did not happen and the money is back on your balance. Safe to try again later. |
| 500 | Your renewal payment went through, but we need to confirm it with a quick manual check. It will be applied within 24 hours or refunded. Please don't try again. | Datacenter, IPv6 and Custom ISP. You WERE charged and our team finishes it. Do NOT retry: a retry only gets the 409 above, so you cannot be charged twice, but it will not help either. |

#### `GET /purchase/isp-extend-price`

Custom ISP and Dedicated ISP renew at a per-IP price times the plan's IPs. Call this before renewing so you can check the balance covers it. It charges nothing.

Custom ISP: the plan's own per-IP price (your personal price when you have one) times its IPs, plus the carrier fee per IP when the plan was bought on a carrier. IP replacements are not part of a renewal. pricePerIp already includes the carrier fee. Launch discounts are for new purchases only, so a renewal is at the plan's normal price (or your personal price). totalPrice is before VAT.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | The ISP plan. |

Request:

```bash
curl "https://catproxies.com/api/v1/user/purchase/isp-extend-price?orderId=3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
// Custom ISP plan (5 IPs, 1 month at 3.60 per IP, bought on a carrier with a 0.35 fee)
{
  "type": "response",
  "message": "Success",
  "payload": {
    "pricePerIp": 3.95,
    "totalPrice": 19.75,
    "customRate": false,
    "ips": 5,
    "periodId": "1_month",
    "periodLabel": "1 Month",
    "days": 30
  }
}

// Dedicated ISP plan
{
  "type": "response",
  "message": "Success",
  "payload": {
    "pricePerIp": 1.6,
    "totalPrice": 16,
    "customRate": false,
    "ips": 10,
    "periodId": "1m",
    "periodLabel": "1 Month",
    "days": 30
  }
}
```

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `pricePerIp` | number | Price per IP for the renewal, before VAT. |
| `totalPrice` | number | What the renewal costs before VAT. On Custom ISP it includes the carrier fee per IP when the plan has a carrier. |
| `customRate` | boolean | true when your personal price applies. Coupons are refused on the renewal then. |
| `listTotal` | number | Only when customRate is true: the total at the normal price, for comparison. |
| `ips` | number | IPs on the plan. A renewal keeps the same IPs. |
| `periodId, periodLabel` | string | The plan length. On Custom ISP periodId is "1_day", "7_days", "1_month", "2_months" or "3_months". |
| `days` | number | Days the renewal adds. |

### Your plans

#### `GET /orders`

Every active plan on the account, newest first. With an API key the stored figures are returned by default and the page size is capped at 100. Pass `liveData=true` when you want bandwidth refreshed from the live plan, which costs one lookup per order and is much slower, so keep it out of a polling loop. `extend_retired: true` marks a plan on a retired product: it works until its end date but cannot be renewed. It is false on every other plan.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `proxyType` | string | no | Only plans of this product. |
| `liveData` | boolean | no | Refresh bandwidth from the live plan. Defaults to false for API keys. Slow: one lookup per order. |
| `page` | number | no | Page number. Default 1. |
| `pageSize` | number | no | Results per page. Default 10, capped at 100 for API keys. |

Request:

```bash
curl "https://catproxies.com/api/v1/user/orders?pageSize=25" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "orders": [
      {
        "id": "3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904",
        "package_title": "Standard Resi 1GB V2",
        "package_proxyType": "v2resi",
        "package_bandwidth": 1,
        "package_days": 90,
        "status": "CONFIRMED",
        "expire": "2026-12-03T12:00:00.000Z",
        "auto_extend": false,
        "auto_extend_mode": null,
        "auto_extend_package_id": null,
        "auto_extend_every_days": null,
        "auto_extend_next_at": null,
        "expiry_notice_enabled": false,
        "auto_extend_available": true,
        "extend_retired": false,
        "proxy": {
          "bandwidth_left": 0.87,
          "expired_at": "2026-12-03T12:00:00.000Z"
        }
      }
    ]
  },
  "pagination": { "page": 1, "pageSize": 25, "Total": 3, "Pages": 1 }
}
```

#### `GET /orders/:id`

The plan plus its proxy credentials. This is what you call after buying, and after renewing to see the new end date. Bandwidth figures are refreshed on every call. `locked: true` means the plan has run out and the credentials are withheld. The plan's real end date is `proxy.expired_at`.

Datacenter plans also carry `ip_list` (your IPs, grouped by country and then city), `country_list` (how many IPs you have per country) and `rotating`. `rotating.endpoints` is the rotating host for each country of your plan (plus `WW` when the plan owns all five countries), picked for this plan, so read it from here instead of hardcoding it. `rotating.bound_ip_conflicts` lists the countries where rotating keeps giving the same IP while an IP is whitelisted.

For Custom ISP and Dedicated ISP the per-IP lines come from GET /generator/isp instead.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Order id. |

Request:

```bash
curl https://catproxies.com/api/v1/user/orders/3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904 \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
// Standard Residential plan
{
  "type": "response",
  "message": "success",
  "payload": {
    "order": {
      "id": "3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904",
      "package_title": "Standard Resi 1GB V2",
      "package_proxyType": "v2resi",
      "package_bandwidth": 1,
      "status": "CONFIRMED",
      "expire": "2026-12-03T12:00:00.000Z",
      "locked": false,
      "extend_retired": false,
      "proxy": {
        "server_ip": null,
        "username": "mtpeqbcevnemewc201120",
        "password": "jonxybfeuu",
        "used_bandwidth": 0.13,
        "bandwidth_left": 0.87,
        "whitelist_ip": [],
        "expired_at": "2026-12-03T12:00:00.000Z",
        "isLiveData": true
      }
    }
  }
}

// Datacenter plan (the proxy part carries the IPs and the rotating hosts)
{
  "type": "response",
  "message": "success",
  "payload": {
    "order": {
      "id": "c5c7c6b5-e488-420c-b436-e90e86031751",
      "package_title": "Datacenter Proxies - 1 Month - 100 IPs - 250GB",
      "package_proxyType": "DatacenterP",
      "package_ips": 100,
      "package_bandwidth": 250,
      "package_days": 30,
      "status": "CONFIRMED",
      "locked": false,
      "proxy": {
        "username": "dcuser482913",
        "password": "dcpass91827",
        "used_bandwidth": 12.4,
        "bandwidth_left": 237.6,
        "whitelist_ip": [],
        "expired_at": "2026-10-11T10:00:00.000Z",
        "ip_list": [
          {
            "iso2": "US",
            "name": "United States",
            "count_ips": 60,
            "cities": [{ "name": "New York", "ips": ["203.0.113.24", "203.0.113.57", "..."] }]
          },
          {
            "iso2": "DE",
            "name": "Germany",
            "count_ips": 40,
            "cities": [{ "name": "Frankfurt", "ips": ["198.51.100.14", "..."] }]
          }
        ],
        "country_list": { "countries": { "US": 60, "DE": 40 }, "total": 100 },
        "rotating": {
          "endpoints": { "US": "dcus.catproxies.com", "DE": "dcde.catproxies.com" },
          "bound_ip_conflicts": []
        },
        "isLiveData": true
      }
    }
  }
}
```

#### `GET /generator/isp`

Custom ISP and Dedicated ISP are sold per IP, and every IP is its own proxy line. This returns them all. `stale: true` means the list came from our last known good copy rather than a fresh lookup; the lines are still valid, so call again shortly for the current one. Straight after buying you may instead get HTTP 202 with `{ pending: true }`, which means the plan is still being set up: wait a few seconds and retry.

Custom ISP lines all use one connection server and one port (1337) for both HTTP and SOCKS5, so port_http and port_socks are the same. The host is the connection server you picked with `region`: `auto` (the default) is the one closest to you, and connection_regions lists the others. Each line's login is tied to its IP, and ip is the address the line exits from. The payload also says how many IP replacements the plan has left (replacements) and whether it is a Clean Score plan (clean_score).

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | The ISP plan. |
| `region` | string | no | Custom ISP only. The connection server: "auto" (default, closest to you) or one of the ids in connection_regions, for example "us-east" or "de". An unknown value falls back to "auto". |

Request:

```bash
# Dedicated ISP
curl "https://catproxies.com/api/v1/user/generator/isp?orderId=3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904" \
  -H "Authorization: Bearer cp_your_api_key_here"

# Custom ISP, through the US East connection server
curl "https://catproxies.com/api/v1/user/generator/isp?orderId=9d8e7f6a-5b4c-4d3e-8f2a-1b0c9d8e7f6a&region=us-east" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
// Dedicated ISP plan
{
  "type": "response",
  "message": "success",
  "payload": {
    "stale": false,
    "auth_mode": "logpas",
    "location": null,
    "quantity": 5,
    "expires_at": "2026-10-04T12:00:00.000Z",
    "proxies": [
      {
        "host": "203.0.113.24",
        "port_http": 61234,
        "port_socks": 62234,
        "login": "isp_7f2a91",
        "password": "Vh3JdQuN"
      }
    ]
  }
}

// Custom ISP plan (2 IPs, Clean Score, 2 replacements bought, 1 used)
{
  "type": "response",
  "message": "success",
  "payload": {
    "stale": false,
    "auth_mode": "logpas",
    "location": "United States - New York City",
    "quantity": 2,
    "expires_at": "2026-11-12T12:00:00.000Z",
    "proxies": [
      {
        "host": "us-east.isp.catproxies.com",
        "port_http": 1337,
        "port_socks": 1337,
        "login": "cpx8f31a2-203.0.113.24",
        "password": "Rk4pX9wq",
        "ip": "203.0.113.24"
      },
      {
        "host": "us-east.isp.catproxies.com",
        "port_http": 1337,
        "port_socks": 1337,
        "login": "cpx8f31a2-203.0.113.57",
        "password": "Rk4pX9wq",
        "ip": "203.0.113.57"
      }
    ],
    "connection_region": "us-east",
    "connection_regions": [
      { "id": "auto", "label": "Auto (closest server)" },
      { "id": "us-east", "label": "US East (Virginia)" },
      { "id": "de", "label": "Germany (Frankfurt)" }
    ],
    "replacements": { "bought": 2, "used": 1, "left": 1, "pending": false },
    "clean_score": true
  }
}
```

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `proxies[].host, port_http, port_socks` | string, number | Where to connect. On Custom ISP the host is the connection server and both ports are 1337. |
| `proxies[].login, password` | string | Credentials for that line. On Custom ISP the password is the same on every line and the login carries the line's IP. |
| `proxies[].ip` | string | Custom ISP: the IP this line exits from. Send it to POST /custom-isp/orders/:id/replace to swap it. |
| `location` | object \| string \| null | Custom ISP: the city as "Country - City". Dedicated ISP: null. |
| `connection_region` | string | Custom ISP: the connection server these lines use. |
| `connection_regions` | array | Custom ISP: every connection server as { id, label }. Pass an id as region. |
| `replacements` | object | Custom ISP: { bought, used, left, pending }. pending is true while a replacement is being confirmed. |
| `clean_score` | boolean | Custom ISP: true on a Clean Score (premium tier) plan. |
| `stale` | boolean | true when the list is our last known good copy. The lines still work. |

#### `POST /custom-isp/orders/:id/replace`

Replaces one IP of a Custom ISP plan with a new IP in the same city. The country, city and UDP support stay the same; the carrier can differ. It uses one of the IP replacements bought with the plan (GET /generator/isp shows how many are left), so it only works while replacements.left is above 0.

The swap usually finishes within the call and answers 200 with the old IP, the new IP and how many replacements are left. Read GET /generator/isp afterwards for the updated lines. Sometimes the swap needs longer to confirm: then the answer is HTTP 202 with `{ checking: true }`. Do not send it again. Our team checks it, and GET /generator/isp shows replacements.pending until it is done.

One replacement at a time per plan, with about a minute between two of them. The plan must be active and not expired.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Order id of a Custom ISP plan. |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `ip` | string | yes | The IP to replace, as proxies[].ip on GET /generator/isp. |

Request:

```bash
curl -X POST https://catproxies.com/api/v1/user/custom-isp/orders/9d8e7f6a-5b4c-4d3e-8f2a-1b0c9d8e7f6a/replace \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "ip": "203.0.113.24" }'
```

Response:

```json
// HTTP 200, swapped
{
  "type": "response",
  "message": "IP replaced",
  "payload": { "oldIp": "203.0.113.24", "newIp": "203.0.113.88", "left": 1 }
}

// Still being confirmed (HTTP 202)
{
  "type": "response",
  "message": "Your replacement is taking longer than usual. Our team is checking it and will sort it out, you don't need to do anything.",
  "payload": { "checking": true }
}
```

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `oldIp` | string | The IP that was removed from the plan. |
| `newIp` | string | The IP that took its place. |
| `left` | number | IP replacements left on the plan. |
| `checking` | boolean | Only on HTTP 202: the swap is still being confirmed. Do not retry. |

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 400 | Pick the IP you want to replace. | ip is missing. |
| 400 | You have no IP replacements left on this plan. | All replacements bought with the plan are used. Replacements cannot be added through the API. |
| 400 | This IP is no longer on your plan. Refresh the page and try again. | The IP is not on the plan any more. Read GET /generator/isp again. |
| 400 | IP replacements are only available on this ISP product. | The plan is not a Custom ISP plan. |
| 400 | This plan has expired. | Expired plans cannot swap IPs. |
| 400 | We couldn't replace this IP right now, your replacement was not used. Please try again later or contact support. | Nothing changed and no replacement was used. Safe to try again later. |
| 404 | Plan not found. | No plan with that id on your account. |
| 409 | A replacement is already running on this plan. Please wait a moment. | Wait for the running one to finish. |
| 429 | Please wait a minute between two replacements. | Wait a minute, then try again. |
| 503 | We couldn't reach the IP list right now. Please try again in a minute. | Nothing changed. Try again shortly. |

#### `POST /orders/:id/reset-password`

Issues a new password for the plan. Available on Standard Residential, Premium Residential, IPv6, Datacenter and Rotating Mobile; Custom ISP and Dedicated ISP do not support it and answer 400. The switch-over is not instant: the new password becomes active within a few minutes, and until then the old one may still work on some locations. Rebuild any proxy strings with the new one. On Standard Residential, IPv6 and Datacenter the flags live on the username, so only the password half changes (on Datacenter the one new password is shared by every IP); on Premium Residential they hang off the password, so re-append them to the new one.

Rotating Mobile works differently: the plan gets a whole new login and your remaining data moves onto it, so the USERNAME changes too. The response then carries both, as payload.username and payload.password. The old login stops working straight away, and your used and total GB carry over. One reset per plan per hour.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Order id. |

Request:

```bash
curl -X POST https://catproxies.com/api/v1/user/orders/3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904/reset-password \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Proxy password reset successfully",
  "payload": { "password": "n3wp4ssw0rd" }
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 429 | Too many requests. Please wait a moment. | More than 3 purchases, renewals or resets in a minute on your account (one shared limit). Wait a minute and try again. |
| 404 | Order not found | No plan with that id on your account. |
| 400 | Password reset is only available for Standard, Premium, Unlimited Residential, IPv6, Datacenter and Mobile plans | The plan is another product (Custom ISP, Dedicated ISP). |
| 400 | This plan has no data left, so there is nothing to move to a new password. Top it up first, then reset. | Rotating Mobile only: a plan with 0 GB left can't be moved to a new login. |
| 409 | This plan was just topped up. Please wait about 5 minutes, then reset the password. | Rotating Mobile only: the plan was renewed in the last 5 minutes. Wait and try again. |
| 429 | You can reset this plan's password once an hour. Please try again in 12 minutes. | Rotating Mobile only: one reset per plan per hour. |
| 502 | We couldn't reset the password right now. Nothing was changed on your plan, please try again in a few minutes. | Rotating Mobile only: the reset did not happen and your login is unchanged. Safe to retry. |
| 400 | This plan has expired. | Expired plans cannot be reset. |
| 400 | This plan has no proxy to reset. | The plan is still being set up. Try again shortly. |
| 502 | Failed to reset password. Please try again. | The reset did not go through. Safe to retry. |
| 500 | Internal server error | Our side failed. Check GET /orders/:id before retrying. |

#### `GET /orders/:id/stats`

Read-only usage analytics for one plan: traffic and requests over time, a log of recent connections, and the sites you used most. Works on Datacenter and IPv6 plans. It never changes the plan and costs nothing.

Pick what you want with view:
• overview (default): uploaded, downloaded and total GB, requests and success rate over time, one row per interval.
• activity: recent connections, 100 per page. Page through with page.
• top-domains: the sites you used most, with requests, success rate, bandwidth and average duration.

The figures come from the live network and lag a little behind real time. available: false means we could not reach the stats source just now; the rest of the payload is then empty, so retry later rather than reading it as zero traffic. metrics is always null on Datacenter and IPv6.

GET /orders/:id/unlimited-metrics is the older name of this endpoint. It takes the same parameters and returns exactly the same thing.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Order id of a Datacenter or IPv6 plan. |

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `view` | string | no | "overview" (default), "activity" or "top-domains". |
| `timeframe` | string | no | How far back to look: "1hour", "6hours", "1day", "1week", "1month" or "3months". Default "1day". Any other value falls back to "1day". |
| `interval` | string | no | overview only. Size of each row: "5min", "30min", "1hour", "6hour", "1day", "1week" or "1month". Default "1hour". Any other value falls back to "1hour". |
| `page` | number | no | activity only. Page number, 100 connections per page. Default 1. |

Request:

```bash
# Traffic over the last week, one row per day
curl "https://catproxies.com/api/v1/user/orders/c5c7c6b5-e488-420c-b436-e90e86031751/stats?timeframe=1week&interval=1day" \
  -H "Authorization: Bearer cp_your_api_key_here"

# Recent connections in the last hour, page 2
curl "https://catproxies.com/api/v1/user/orders/c5c7c6b5-e488-420c-b436-e90e86031751/stats?view=activity&timeframe=1hour&page=2" \
  -H "Authorization: Bearer cp_your_api_key_here"

# The sites you used most today
curl "https://catproxies.com/api/v1/user/orders/c5c7c6b5-e488-420c-b436-e90e86031751/stats?view=top-domains" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
// view=overview (Datacenter or IPv6, so metrics is null)
{
  "type": "response",
  "message": "SUCCESS",
  "payload": {
    "metrics": null,
    "series": [
      {
        "timestamp": "2026-09-10 00:00:00",
        "uploaded_gb": 0.000473,
        "downloaded_gb": 0.283713,
        "total_gb": 0.284186,
        "requests": 245,
        "success_rate": 100
      },
      {
        "timestamp": "2026-09-11 00:00:00",
        "uploaded_gb": 0.000391,
        "downloaded_gb": 0.201577,
        "total_gb": 0.201968,
        "requests": 188,
        "success_rate": 98.9
      }
    ],
    "timeframe": "1week",
    "interval": "1day",
    "available": true
  },
  "pagination": {}
}

// view=activity
{
  "type": "response",
  "message": "SUCCESS",
  "payload": {
    "activity": [
      {
        "timestamp": "2026-09-11 08:00:56",
        "domain": "example.com",
        "outcome": "SUCCESS",
        "protocol": "SOCKS5",
        "downloaded_bytes": 228,
        "uploaded_bytes": 77,
        "duration_ms": 284,
        "client_ip": "203.0.113.77"
      }
    ],
    "pagination": { "total": 164, "per_page": 100, "current_page": 2, "last_page": 2 },
    "timeframe": "1hour",
    "available": true
  },
  "pagination": {}
}

// view=top-domains
{
  "type": "response",
  "message": "SUCCESS",
  "payload": {
    "topDomains": [
      {
        "domain": "example.com",
        "requests": 29,
        "success_rate": 100,
        "total_bandwidth": 242297,
        "total_downloaded": 186472,
        "total_uploaded": 55825,
        "avg_duration_ms": 145.2
      }
    ],
    "summary": {
      "total_domains": 13,
      "total_requests": 463,
      "total_bandwidth": 284284595,
      "total_bandwidth_gb": 0.2843
    },
    "timeframe": "1day",
    "available": true
  },
  "pagination": {}
}

// Stats source not reachable right now (retry later)
{
  "type": "response",
  "message": "SUCCESS",
  "payload": { "metrics": null, "series": [], "timeframe": "1day", "interval": "1hour", "available": false },
  "pagination": {}
}
```

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `available` | boolean | false = the stats source could not be reached just now and the rest is empty. Retry later; it does not mean zero traffic. |
| `metrics` | object \| null | overview only. Always null on Datacenter and IPv6. |
| `series[].timestamp` | string | Start of the row, as a "YYYY-MM-DD HH:MM:SS" string (a space, not a T). |
| `series[].uploaded_gb, downloaded_gb, total_gb` | number | Traffic in that row, in GB. |
| `series[].requests` | number | Requests in that row. |
| `series[].success_rate` | number | Share of successful requests, in percent (0 to 100). |
| `activity[].timestamp` | string | When the connection happened, as a "YYYY-MM-DD HH:MM:SS" string. |
| `activity[].outcome` | string | Result of the connection, for example SUCCESS or ERROR. |
| `activity[].downloaded_bytes, uploaded_bytes` | number | Traffic of that connection, in bytes. |
| `activity[].duration_ms` | number | How long the connection took, in milliseconds. |
| `activity[].client_ip` | string | The address the connection came from (your side). |
| `pagination` | object | activity only. total rows, per_page (100), current_page and last_page. |
| `topDomains[].success_rate` | number | Share of successful requests to that site, in percent. |
| `topDomains[].total_bandwidth, total_downloaded, total_uploaded` | number | Traffic to that site, in bytes. |
| `topDomains[].avg_duration_ms` | number | Average request duration, in milliseconds. |
| `summary` | object \| null | top-domains only. Totals for the timeframe as name: number pairs, for example total_domains, total_requests, total_bandwidth (bytes) and total_bandwidth_gb. Only numbers are passed on and the set of names can change, so read them defensively. null when there is none. |

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 404 | Order not found | No plan with that id on your account. |
| 400 | Usage statistics are available for Datacenter, IPv6 and Unlimited Residential plans only. | The plan is another product. |
| 400 | This plan has no proxy yet. | The plan is still being set up. Try again shortly. |
| 500 | Internal server error | Our side failed. Safe to retry. |

#### `GET /orders/:id/download`

The invoice for this order, as a PDF. This is not the proxy list: for that use GET /orders/:id, or GET /generator/isp on an ISP plan. Returns 400 when the order has no invoice, which is normal for an order paid from balance.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Order id. |

Request:

```bash
curl -OJ https://catproxies.com/api/v1/user/orders/3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904/download \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
(binary PDF, Content-Type: application/pdf)

When the order has no invoice:
{ "type": "error", "message": "No invoice available for this order" }
```

### Targeting options

#### `GET /generator/budget/targeting-options`

The country, state, city and ASN values you can put in a Standard Residential username. Pass the order so the list matches that plan. The tree is large and changes rarely, so cache it rather than fetching it per request.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | no | A Standard Residential plan. Without it you get the general list. |

Request:

```bash
curl "https://catproxies.com/api/v1/user/generator/budget/targeting-options?orderId=3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "countries": [
      {
        "code": "us",
        "name": "United States",
        "asns": [
          { "code": "7922", "name": "AS7922 Comcast Cable Communications, LLC" }
        ],
        "states": [
          {
            "code": "california",
            "name": "California",
            "cities": [
              { "code": "losangeles", "name": "Los Angeles" }
            ]
          }
        ]
      }
    ]
  }
}
```

#### `GET /generator/mobile-locations/countries`

Countries with live mobile coverage. Use the codes exactly as returned. The lists are cached on our side for an hour and change rarely, so fetch them once at startup rather than per request.

Request:

```bash
curl https://catproxies.com/api/v1/user/generator/mobile-locations/countries \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": { "countries": [ { "code": "US", "name": "United States" } ] }
}
```

#### `GET /generator/mobile-locations/regions`

Regions or states you can target inside a mobile country. Full names in lowercase, not two-letter codes.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `country` | string | yes | Country code from the countries endpoint. |

Request:

```bash
curl "https://catproxies.com/api/v1/user/generator/mobile-locations/regions?country=US" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": { "regions": [{ "code": "california", "name": "California" }] }
}
```

#### `GET /generator/mobile-locations/cities`

Cities you can target. Spaces are written as a plus sign, never a hyphen.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `country` | string | yes | Country code. |
| `region` | string | no | Region code. |

Request:

```bash
curl "https://catproxies.com/api/v1/user/generator/mobile-locations/cities?country=US&region=california" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": { "cities": [{ "code": "los+angeles", "name": "Los Angeles" }] }
}
```

#### `GET /generator/mobile-locations/isps`

Carriers available for a country, and optionally a city. Pin a carrier at country level for the most reliable results; carrier coverage in one particular city moves around.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `country` | string | yes | Country code. |
| `region` | string | no | Region code. |
| `city` | string | no | City code. |

Request:

```bash
curl "https://catproxies.com/api/v1/user/generator/mobile-locations/isps?country=US" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": { "isps": [{ "code": "at&t+wireless", "name": "AT&T Wireless" }] }
}
```

### Automatic renewal

#### `GET /orders/:id/auto-extend`

Whether this plan renews itself from your balance, in which mode, and whether you asked for the heads-up email. `supportsMode` is true on the products that offer a choice of mode. `renewable` says whether automatic renewal can be switched on for this plan. It is false on Datacenter, IPv6 and plans on a retired product: automatic renewal is not offered for them (Datacenter and IPv6 plans can still be renewed by hand with POST /purchase/extend). `packages` lists the refill sizes you may choose from: exactly the ones on sale for that product.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Order id. |

Request:

```bash
curl https://catproxies.com/api/v1/user/orders/3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904/auto-extend \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": {
    "notice": false,
    "enabled": true,
    "mode": "schedule",
    "packageId": "cdd0c81b-43fd-4bef-9709-2173a5ad3c5f",
    "everyDays": 30,
    "nextAt": "2026-10-09T12:00:00.000Z",
    "supportsMode": true,
    "renewable": true,
    "packages": [
      { "id": "cdd0c81b-43fd-4bef-9709-2173a5ad3c5f", "title": "Standard Resi 1GB", "bandwidth": 1, "days": 90, "price": 2.5 }
    ]
  }
}
```

#### `PATCH /orders/:id/auto-extend`

Changes either switch. Every field is optional: send only what you are changing.

`notice` turns the "your plan runs out soon" email on or off. It is off by default and no money is involved.

`enabled` turns automatic renewal on or off. On Residential and Mobile you also choose a mode. "term" buys the smallest package we sell, a couple of days before the plan expires, which is the cheapest way to keep it alive. "schedule" buys a size you pick every `everyDays` days, and needs `packageId` (one of the ids from `packages`) and `everyDays` (1 to 180). Custom ISP and Dedicated ISP renew one way only, so the mode is ignored there. Datacenter, IPv6 and plans on a retired product are refused, and so is any plan that has ended, belongs to a reseller account, or has a term of two days or less (`auto_extend_available` on the plans list tells you in advance). Switching renewal off keeps your chosen size and interval, so switching it back on restores them.

Nothing is charged when you call this. The balance is only taken at the moment a renewal actually runs, and if it is short then that renewal simply fails.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Order id. |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `enabled` | boolean | yes | On or off. |
| `notice` | boolean | no | Turn the "runs out soon" email on or off for this plan. Independent of renewal. |
| `mode` | string | no | "term" or "schedule". Used when enabling a Residential or Mobile plan; defaults to "term". |
| `packageId` | string | no | Required with mode "schedule". One of the ids from `packages` on the GET. |
| `everyDays` | integer | no | Required with mode "schedule". Days between refills, 1 to 180. |

Request:

```bash
curl -X PATCH https://catproxies.com/api/v1/user/orders/3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904/auto-extend \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true, "mode": "schedule", "packageId": "cdd0c81b-43fd-4bef-9709-2173a5ad3c5f", "everyDays": 30 }'
```

Response:

```json
{
  "type": "response",
  "message": "success",
  "payload": { "notice": false, "enabled": true, "mode": "schedule", "everyDays": 30, "renewable": true, "supportsMode": true }
}
```

## Support

Questions about the API: support@catproxies.com. The human version of this document is at https://catproxies.com/docs/api


---

# CatProxies Reseller API

Documentation for the CatProxies **Reseller API**: the wholesale API for partners who
resell our proxies to their own customers, at the reseller prices set for their account.

This document is the complete reference. It is generated from the same source as the
human page at https://catproxies.com/docs, so the two always agree.

- Base URL: `https://catproxies.com/api/v1/public`
- Auth: `Authorization: Bearer cp_your_api_key_here`
- Keys are created at https://catproxies.com/reseller/api
- Rate limit: 1000 requests per hour per key.

**If you are an ordinary customer rather than a reseller, you want the User API
instead: https://catproxies.com/docs/api/llms.txt** That one bills at normal retail
prices and needs no reseller agreement.

## How the reseller model works

You sell from the catalogue assigned to your account, not from an open pool. Your
`/store` lists exactly the packages an admin has enabled for you, each at the reseller
price we set for you. Every order is paid from your prepaid balance.

So the whole API surface is: read your store, buy a package, get credentials, then
manage and extend. There is nothing to provision or configure beyond that.

You can also tag each order with the end customer you resold it to, which is what the
customer endpoints are for. That is purely our-side metadata and is never sent upstream.

## Authentication

Send the key as a bearer token on every request:

```
Authorization: Bearer cp_your_api_key_here
```

- Keys start with `cp_`. Up to 5 per reseller account.
- A key can spend your balance. Treat it like a password.
- Keys are per reseller and are rejected on the customer-facing User API.

## Rate limits

1000 requests per hour per key, on a rolling window. Every response carries:

| Header | Meaning |
| --- | --- |
| `X-RateLimit-Limit` | Requests allowed per hour |
| `X-RateLimit-Remaining` | How many are left |
| `X-RateLimit-Reset` | When capacity returns (ISO 8601) |

Going over returns `429` with `Retry-After`. `GET /account` reports the same figures
in `rateLimits`. The mobile targeting endpoints share a tighter limit of 60 requests
per 10 minutes, so cache those lists.

## Products

Every product in `/store` has a `proxyType`. Use that exact value to filter the store
(`?proxyType=`) and to tell which product an order is.

| Product | proxyType | Sold by |
| --- | --- | --- |
| Standard Residential | `v2resi` | Bandwidth (GB) |
| Premium Residential | `resix` | Bandwidth (GB) |
| Custom ISP | `Isp` | Per IP, 1 day to 3 months, by city and optional carrier |
| Dedicated ISP | `IspP` | Per IP, per month, pool based |
| Datacenter | `DatacenterP` | IPs plus a bandwidth pool |
| IPv6 | `Ipv6p` | Bandwidth or unlimited |
| Rotating Mobile | `RotatingMobile` | Bandwidth (GB) |

## Quick start

```bash
KEY="cp_your_api_key_here"
API="https://catproxies.com/api/v1/public"

# 1. See what you can sell, and your price
curl -s -H "Authorization: Bearer $KEY" "$API/store"

# 2. Buy a package (paid from your balance)
curl -s -X POST "$API/order" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "PACKAGE_ID_FROM_STORE" }'

# 3. Read the credentials back
curl -s -H "Authorization: Bearer $KEY" "$API/order/ORDER_ID"
```

### Buying is not idempotent (important)

A purchase sets up a live plan and charges your balance. If a call times out or the
outcome is unclear, look the order up with `GET /orders` before retrying, or you may
pay for two.

## Errors

Errors are JSON with a readable `message` and `"type": "error"`.

| Status | Meaning | What to do |
| --- | --- | --- |
| 400 | The request was wrong | Read `message`. Usually a missing or invalid field. |
| 401 | Bad or inactive key | Check the key and that it is still active. |
| 400 | Insufficient balance | The reseller API returns this as a 400 with message "Insufficient balance", not a 402. Top up your reseller balance. |
| 403 | Profile or country | POST /order also answers 403 for "Please add a name and address to complete your profile", for a blocked billing country, and for a product not available to your account. None of these are retryable. |
| 404 | Order not found OR unsupported | The whitelist endpoints answer 404 with "Order not found or does not support IP whitelisting" for a product that has no whitelist, not only for a missing order. |
| 404 | Not found | The order or package id does not exist on your account. |
| 409 | Conflict | Read the message. On `POST /order/custom` the charge stands and a human is finishing the order: do NOT retry. On a Datacenter, IPv6 or Custom ISP renewal another renewal of the same order is still running, or an earlier one is under review: that call was not charged; wait, or contact us if it is under review. |
| 429 | Rate limited | Wait for `Retry-After`, then continue. |
| 500 or 502 | Our side failed | Safe to retry a read. Do NOT blindly retry an order or a renewal: read the message first. On `POST /order/:id/extend`, "Failed to extend proxy plan. Your balance has been refunded." means nothing happened (retry later), but "Your renewal payment went through, but it needs manual review..." means you were charged and our team is finishing it (do NOT retry). |

## Product guides

Buying is the same call for every product. What differs is the extra body a purchase needs, the host and port you connect to, and where the targeting flags go.

Hosts and per-IP lines come from `GET /order/:id` and `GET /order/:id/proxies`. All of them are plain unbranded IPs, safe to pass straight to your own customers. On Custom ISP you can also use your own hostname, with the same Auto option we use (see Custom ISP).

### What can be renewed

| Product | Renewable | How |
| --- | --- | --- |
| Custom ISP | Yes | `POST /order/:id/extend` with `{}`. Same package and IPs. Replacements are not renewed. |
| Dedicated ISP | Yes | `POST /order/:id/extend` with `{}`. Another fixed 30 days at `resellerPrice` × the order IP count. |
| Standard Residential | Yes | Adds bandwidth. Pick a bandwidth pack from `GET /order/:id/extendOptions` and send its `packageId` to `POST /order/:id/extend`. |
| Premium Residential | Yes | Exactly like Standard Residential, with no extra body parameters. |
| Datacenter | Yes | `POST /order/:id/extend` with `{}`. Resets the data to the plan's full GB; your remaining days carry over. |
| IPv6 | Yes | `POST /order/:id/extend` with `{}`. Unlimited plans get days added, bandwidth plans get GB added (at most 5000 GB in total). |
| Rotating Mobile | Yes | With just the `packageId`, like buying it. |

### Custom ISP (`Isp`)

#### What it is

Custom ISP (`proxyType: Isp`) is static ISP proxies where your customer picks everything: the **tier** (Standard, or Clean Score with IPs that have a fraud score of 0), the **plan length** (1 day, 7 days, 1 month, 2 months or 3 months), the **city**, and optionally the **carrier** (ASN) and **UDP** support. Up to **5 IP replacements** can be bought with the plan. You pay `pricePerIp × quantity` + `carrierFeePerIp × quantity` (only with a carrier) + `replacements × replacementPrice`, from your balance, no VAT. You can buy it through the API or from the Store page in your reseller panel.

In the API the tier is `standard`, or `premium` which is sold as Clean Score: IPs checked right before delivery, with a fraud score of 0 when delivered; never check them on a score site through the proxy. The plan length is `1_day`, `7_days`, `1_month`, `2_months` or `3_months`.

#### Buying

**Complete Custom ISP workflow**

```bash
# 1. Your plans and prices, and the cities in stock.
#    tier=premium for Clean Score, udp=1 for UDP-capable IPs only.
curl "https://catproxies.com/api/v1/public/custom-isp/catalog?tier=standard&udp=0" \
  -H "Authorization: Bearer cp_your_api_key_here"
# -> plans.standard["1_month"] = { packageId, pricePerIp: 3.20, days: 30, minQty, maxQty, replacementPrice: 1.10 }
# -> carrierFeePerIp: 0.35, countries[].cities[].location = "United States - New York City"

# 2. Optional: carriers in that city (same tier and udp as you will buy)
curl "https://catproxies.com/api/v1/public/custom-isp/carriers?tier=standard&location=United%20States%20-%20New%20York%20City" \
  -H "Authorization: Bearer cp_your_api_key_here"
# -> carriers[] = { asn: 7922, name, stock }

# 3. Buy 5 IPs on that carrier, with 2 IP replacements
curl -X POST "https://catproxies.com/api/v1/public/order" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "...", "ispData": { "quantity": 5, "location": "United States - New York City",
    "asn": 7922, "udp": false, "replacements": 2 } }'
# Charged: 3.20 × 5 + 0.35 × 5 + 1.10 × 2 = 19.95
```

| Field | Notes |
| --- | --- |
| `packageId` | From GET /custom-isp/catalog, plans[tier][length].packageId. One package is one tier and one length. Only tiers and lengths you have a reseller price for are listed. |
| `ispData.quantity` | Required. Number of IPs, within the plan minQty and maxQty. |
| `ispData.location` | Required. The city exactly as the catalog returns it, "Country - City". |
| `ispData.asn` | Optional. A carrier from GET /custom-isp/carriers for that city. Adds carrierFeePerIp per IP. |
| `ispData.udp` | Optional. true = only IPs with UDP support. Ask the catalog and carriers with udp=1 so the counts match. |
| `ispData.replacements` | Optional, 0 to 5. Each costs the plan replacementPrice. Used with POST /order/:id/replace. Not renewed. IP replacements can only be bought together with the order: they can't be added later, so choose how many you need when you buy. |
| `Tiers` | standard = "Standard". premium = "Clean Score": IPs checked right before delivery, with a fraud score of 0 when delivered. Checking an IP on a score site through the proxy raises its score, so check it from a connection that is not using the proxy. clean_score is true on these orders. |
| `Stock` | The catalog counts are at most a minute old. POST /order checks stock again before charging; a city or carrier that ran out is refused and nothing is charged. |
| `Delivery` | If delivery is refused or out of stock, your balance is refunded automatically. If the result is unclear, the order is held for manual review and delivered within 24 hours or refunded. Do NOT order again. |

#### Proxy format / connecting

Each line from `/order/:id/proxies` is one IP. All lines use the same port, **1337, for both HTTP and SOCKS5**. `host` is a plain server IP, or your own hostname once you set one up (below), never one of our names. The order has one password; each line's `login` carries its IP, so always build each proxy string from its own line. `ip` is the address the line exits from.

```bash
# Get the lines (one per IP). ?region= picks the connection server, default auto.
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/proxies?region=auto" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

```
<host>:<port>:<login>:<password>                   # line format
http://<login>:<password>@<host>:1337               # HTTP / HTTPS
socks5://<login>:<password>@<host>:1337             # SOCKS5, same port
```

| Field | Notes |
| --- | --- |
| `?region=` | Connection server for the lines; connection_regions lists them. Only the route changes, not the exit IP. auto (default): with your own hostname it is your hostname itself, which answers with the server closest to YOUR customer. Without one, it is the server IP closest to where each line's IP is. |
| `host` | A plain server IP, picked per order. Servers can change, so read the lines from the API each time instead of saving them. With your own hostname set up, host is that hostname (or <location>.<your hostname>), which always follows our server changes. |
| `GET /order/:id` | port "1337", auth_mode "logpas" and a note pointing to /proxies and /replace. `package_region` holds the pick as JSON: { kind: "customisp", tier, location, asn, udp, length, replacements, carrierName }. |

##### Your own hostname (white label with Auto)

Put the lines on a subdomain of your own domain, e.g. `isp.yourbrand.com`. We serve the same records we use: `isp.yourbrand.com` is Auto (it answers with the server closest to whoever connects) and `<location>.isp.yourbrand.com` is one location (`us-east`, `us-central`, `us-west`, `ca`, `uk`, `nl`, `de`, `fi`, `sg`, `jp`, `au`). Dead servers are taken out for you, like on ours. Nothing in the lines or in a DNS lookup of the A records points back to us. You can also do this from the API page of your reseller panel.

```bash
# 1. Add your hostname. The reply lists the 2 NS records to add.
curl -X POST "https://catproxies.com/api/v1/public/custom-isp/domain" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "isp.yourbrand.com" }'
# -> status "pending", records: [ { type: "NS", name: "isp.yourbrand.com", value: "kiki.bunny.net" },
#                                 { type: "NS", name: "isp.yourbrand.com", value: "coco.bunny.net" } ]

# 2. Add those 2 NS records at your DNS provider, then check. It switches on by itself once they are seen.
curl "https://catproxies.com/api/v1/public/custom-isp/domain" \
  -H "Authorization: Bearer cp_your_api_key_here"
# -> status "active", hosts: { auto: "isp.yourbrand.com", "us-east": "us-east.isp.yourbrand.com", ... }
```

| Field | Notes |
| --- | --- |
| `domain` | A subdomain of a domain you own (at least 3 parts, e.g. isp.yourbrand.com). One per account. Do not add A or CNAME records for it: the 2 NS records hand that name to us. |
| `status` | none, pending (waiting for your NS records; lines keep using plain server IPs) or active (lines use your hostname). GET checks again on every call while pending, at most every 20 seconds. DNS changes can take up to an hour. |
| `DELETE /custom-isp/domain` | Removes it. Your lines go back to plain server IPs right away; remove the NS records on your side too. |

#### Renewing

```bash
# Renew: same package, same IPs. Check the price, then send {}
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/extendOptions" \
  -H "Authorization: Bearer cp_your_api_key_here"

# Renew (empty body)
curl -X POST "https://catproxies.com/api/v1/public/order/ORDER_ID/extend" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'
# Charged: 3.20 × 5 + 0.35 × 5 = 17.75 (replacements are not renewed)
```

| Field | Notes |
| --- | --- |
| `Renewal` | POST /order/:id/extend with {}. Same package and IPs at your price per IP × IPs, plus carrierFeePerIp × IPs when the order has a carrier. One renewal at a time; an order under review cannot be renewed; if the result is unclear the payment is kept and our team checks it. If only some IPs could be renewed you still get 200: the order is flagged and our team completes the rest, so do not renew again. |

#### Good to know

**Please do not check the IPs through the proxy**

We check every IP before delivery. Visiting IP check or fraud score sites (IPQS and similar) through the proxy can raise the IP's fraud score, because those sites log every visit. To check an IP, look it up from another device or a connection that is not using the proxy, and ask your customers to do the same.

| Field | Notes |
| --- | --- |
| `IP locations` | Locations (country and city) are strictly based on the ipinfo.io database. Other IP databases can show a different city for the same IP. |
| `Carrier (ASN)` | A renewal keeps the same IPs. A replacement IP stays in the same city with the same UDP support, but for now it can come from a different carrier (ASN). |
| `Stock and delivery errors` | Sometimes a city or carrier runs out, or delivery fails. POST /order checks stock before charging, so most of these are refused with nothing charged. If delivery is refused after the charge, your balance is refunded straight away and the order is cancelled: buy again in a few minutes, or pick another city or carrier. If the result is unclear, we check whether the IPs were delivered and finish or refund the order within 24 hours. Do not order again in that case. |

```bash
# Swap one IP for a new one in the same city (uses one replacement)
curl -X POST "https://catproxies.com/api/v1/public/order/ORDER_ID/replace" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "ip": "203.0.113.24" }'
```

| Field | Notes |
| --- | --- |
| `Replace IP` | POST /order/:id/replace { ip }. 200 { oldIp, newIp, left }. 202 { checking: true } = still being confirmed, do NOT retry. 400 when it is not a Custom ISP order or ip is missing. The new IP is in the same city with the same UDP support; the carrier can differ. |
| `Not available` | No reset-password, no IP whitelist and no stats endpoint on Custom ISP. |
| `Reseller panel` | Also sold on the Store page of your reseller panel, with the same prices and the same rules as the API. |

### Dedicated ISP (`IspP`)

#### What it is

Dedicated ISP (`proxyType: IspP`) is a **per-IP** product: order any quantity from **1 to 100 IPs** at a flat `resellerPrice` per IP (total = `resellerPrice × quantity`). It is **pool-based (no country to pick)**, the term is a **fixed 30 days**, and it is **username:password only**. Each IP is a dedicated line served from `GET /order/:id/proxies`.

#### Buying

**Complete Dedicated ISP workflow**

```bash
# 1. List Dedicated ISP packages - note resellerPrice (per IP). locations[] is
#    EMPTY (pool-based, no country). Any quantity 1-100 costs the same per IP.
curl "https://catproxies.com/api/v1/public/store?proxyType=IspP" \
  -H "Authorization: Bearer cp_your_api_key_here"

# 2. Buy 5 IPs - pass only the quantity.
curl -X POST "https://catproxies.com/api/v1/public/order" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "...", "ispData": { "quantity": 5 } }'
# Charged: resellerPrice × 5. Term: a fixed 30 days.
```

| Field | Notes |
| --- | --- |
| `ispData.quantity` | Number of IPs, 1-100. Flat per-IP price across the whole range. Defaults to 1 if omitted - always send it explicitly. |
| `Term` | Fixed 30 days. Not selectable. |
| `Subnet plans` | A /24 block (isSubnet: true) is NOT buyable via the API (returns 400); arrange subnets with support. |
| `Delivery problems` | If the IPs cannot be delivered right away, the error says the order was flagged to our team. Your payment is kept and we retry automatically for up to 3 hours. If it still cannot be delivered, your balance is refunded automatically. If the result is unclear, our team delivers or refunds it within 24 hours. Never re-order: POST /order is not idempotent, so a retry is a new order and a new charge. |

#### Proxy format / connecting

Each line from `/order/:id/proxies` is one dedicated proxy with its own login/password. HTTP and SOCKS5 run on **different ports**: use `port_http` for HTTP/HTTPS and `port_socks` for SOCKS5 and UDP-over-SOCKS5. Always read both from the API rather than assuming they match. There is no gateway host and no username-appended targeting.

```bash
# Get the proxy lines (host / port / login / password per IP).
#    Normally ready immediately; if you get HTTP 202 { pending: true }, retry
#    in a few seconds.
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/proxies" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

```
http://<login>:<password>@<host>:<port_http>     # HTTP / HTTPS
socks5://<login>:<password>@<host>:<port_socks>  # SOCKS5 (UDP supported)
```

| Field | Notes |
| --- | --- |
| `Credentials` | Not on GET /order/:id. Fetch the host:port:login:password lines from GET /order/:id/proxies. |
| `Provisioning` | GET /order/:id/proxies may return HTTP 202 { pending: true, proxies: [] } for a few seconds after purchase; retry. stale: true = served from cache during a brief upstream blip, lines still valid. |

#### Renewing

```bash
# 1. Check the renewal cost (resellerPrice × 5 for another 30 days).
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/extendOptions" \
  -H "Authorization: Bearer cp_your_api_key_here"

# 2. Renew - packageId is resolved from the order, so send an empty body.
curl -X POST "https://catproxies.com/api/v1/public/order/ORDER_ID/extend" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'
# Charged: resellerPrice × 5 again, +30 days.
```

| Field | Notes |
| --- | --- |
| `Renewal` | POST /order/:id/extend with {}. Cost = resellerPrice × the order IP count, for another fixed 30 days. Not idempotent - serialize renew calls per order. |

#### Good to know

| Field | Notes |
| --- | --- |
| `IP locations` | Locations are strictly based on the ipinfo.io database. Other IP databases can show a different location for the same IP. |

### Standard Residential (`v2resi`)

#### What it is

Standard Residential (`proxyType: v2resi`) is a **bandwidth** product. Buy and extend it with just the `packageId` - no extra body fields. Geo and session targeting are **appended to the username**, so one set of credentials covers every country and session. Rotating IP per request by default. Username:password only: there is no IP whitelist and no usage stats on this product.

#### Proxy format / connecting

##### Connection ports

Use the host from `GET /order/:id` (`proxyCredentials.hostip`). Pick the port for the protocol and rotation you want.

| Protocol | Rotating port | Sticky port |
| --- | --- | --- |
| HTTP / HTTPS | `9000` | `10000` |
| SOCKS5 | `11000` | `12000` |

#### Targeting

##### Targeting - append to the username

Keep the password as-is and append flags to the **username**. `-type-residential` is **required**; the rest are optional.

| Target | Flag | Value format |
| --- | --- | --- |
| Type (required) | `-type-residential` | always include this |
| Country | `-country-us` | ISO-2 code, lowercase |
| State / region | `-state-california` | lowercase |
| City | `-city-losangeles` | lowercase, no spaces |
| ASN / carrier | `-asn-7922` | ASN number only |
| Sticky session | `-lifetime-30-session-ab12` | minutes (max 1440) + any id |
| UDP over SOCKS5 | `-requireUdp-true` | SOCKS5 only, use port 12000 |

Add `-lifetime-<minutes>-session-<id>` to hold the same exit IP (lifetime up to 1440 minutes = 24h); omit the sticky flags for a fresh IP on every request. For UDP traffic, connect over SOCKS5 on port `12000` and append `-requireUdp-true` to the username; this is optional and only needed for clients that use SOCKS5 UDP (leave it off for normal HTTP/S traffic). Discover valid countries, cities and ASNs with `GET /gresi-targeting-options?orderId=...` (see the Targeting endpoints).

**Example**

```bash
# Credentials from GET /order/:id
#   username: mtpeqbcevnemewc201120   password: jonxybfeuu
#   host:     HOST = proxyCredentials.hostip from GET /order/:id. It is a plain
#             unbranded IP on purpose, so your own customers never see where the
#             plan came from. HTTP 9000 rotating, 10000 sticky.

# Rotating IP in the US (new IP per request) - HTTP port 9000
curl -x HOST:9000 \
  -U "mtpeqbcevnemewc201120-type-residential-country-us:jonxybfeuu" \
  https://ipinfo.io

# Sticky IP in Los Angeles for 30 minutes - sticky HTTP port 10000
curl -x HOST:10000 \
  -U "mtpeqbcevnemewc201120-type-residential-country-us-state-california-city-losangeles-lifetime-30-session-ab12:jonxybfeuu" \
  https://ipinfo.io
```

| Feature | Notes |
| --- | --- |
| `Targeting reference` | GET /gresi-targeting-options?orderId=... returns the available countries, cities and ASNs for the order. |
| `City / state name lists` | The endpoint above returns the exact set for your order. For a ready-made list of city and US-state name tokens (same lowercase, no-separator format), download the Premium Residential targeting reference - its cities / us_states / us_state_cities lists apply here too. |

#### Renewing

| Feature | Notes |
| --- | --- |
| `Extending` | Adds bandwidth. GET /order/:id/extendOptions returns your bandwidth packs; POST /order/:id/extend with the chosen packageId. |

#### Good to know

| Feature | Notes |
| --- | --- |
| `Rotate password` | POST /order/:id/reset-password issues a new base password (old one stops immediately; new one activates in 1-2 minutes). The targeting flags live on the username, so only the password half of your proxy strings changes. |
| `Authentication` | Username:password only. There is no IP whitelist and no usage stats endpoint on Standard Residential. |

### Premium Residential (`resix`)

#### What it is

Premium Residential is a **bandwidth** product. Buy and extend it exactly like Standard Residential - no extra body parameters. Geo, session and optional quality filters are **appended to the password**, so a single set of credentials covers every country, session and mode. HTTP and SOCKS5 are both supported. The region is chosen by which endpoint you connect to.

#### Proxy format / connecting

##### Connection endpoints

`GET /order/:id` returns `proxyCredentials.hostip` / `port` (the Americas HTTP gateway IP by default) plus an `endpoints` object with the `ip:port` for every region and protocol. The host is always the IP from your order; pick the endpoint whose region matches your target country, on HTTP or SOCKS5, using the ports below.

| Region | HTTP port | SOCKS5 port |
| --- | --- | --- |
| Americas | `6011` | `6014` |
| Europe | `6012` | `6015` |
| Asia / Africa / Oceania | `6013` | `6016` |

##### Dedicated ISP pools

Besides the residential pool, every Premium Residential order can also use six fast, country-locked ISP pools with the same username and password and the same bandwidth. These are part of Premium Residential, not the separate Dedicated ISP product. `GET /order/:id` lists them in `proxyCredentials.isp_pools` (an array of `id`, `name`, `country`, `protocol` and `endpoint`). Each pool is its own `ip:port`, and the port decides the pool and its country.

| Pool (id) | Country | Port |
| --- | --- | --- |
| United States (`us`) | US | `6000` |
| United States Tickets (`us_tickets`) | US | `6001` |
| United Kingdom (`uk`) | GB | `6003` |
| Germany (`de`) | DE | `6006` |
| Germany DTAG (`de_dtag`) | DE | `6007` |
| Australia (`au`) | AU | `6002` |

**How the ISP pools differ**

- **HTTP only.** The pools have no SOCKS5 port.
- **No targeting.** Send the bare password (rotating) or `PASSWORD-session-a1b2c3d4_lifetime-1h` (sticky). Country, city, state, filters and device are not used by the pools, so leave them out.
- **Read the endpoint from `isp_pools`.** Don't hard-code it, as we may move a pool to a healthier server.

```bash
curl -x ISP_POOL_ENDPOINT -U "USERNAME:PASSWORD-session-a1b2c3d4_lifetime-1h" http://ip-api.com/json
```

#### Targeting

##### Targeting - append to the password

Keep the username as-is and append flags to the **password**. The **first** flag joins the password with a `-` and every flag after it with a `_`. All flags are optional, and a bare password returns a worldwide IP.

| Target | Flag | Value format |
| --- | --- | --- |
| Country | `country-us` | ISO-2 code, lowercase. Optional, omit for worldwide. |
| US state | `state-california` | state name, lowercase, no spaces (newyork). US only, not with a city. |
| City | `city-losangeles` | lowercase, no spaces or separators. Not with a state. |
| Sticky session | `session-a1b2c3d4` | exactly 8 lowercase letters/digits. One id = one IP. |
| Session lifetime | `lifetime-1h` | how long the sticky IP is held: minutes or hours (30m, 1h, 24h), up to 168h. |
| Quality filter | `streaming-1` | best-effort cleaner IPs. |
| Speed filter | `direct-1` | best-effort lower latency. |
| Auth filter | `skipispstatic-1` | best-effort fewer blocks. |
| Device | `device-windows` | windows, android, ios, macos or linux. Best-effort. Put it last. |

**How the flags combine**

- **First flag uses `-`, the rest use `_`** - e.g. `-country-gb_city-london_session-a1b2c3d4_lifetime-1h`.
- **City and state are mutually exclusive**, and both require a country. Use one or the other, never both.
- **Multi-word cities/states have no spaces or separators** - `city-losangeles`, `state-newyork`.
- **Rotating vs sticky.** No `_session-` = a fresh IP for every new connection (a connection your client keeps open keeps its IP, so open a new connection when you want a new IP). Add a unique 8-char `_session-<id>` (optionally `_lifetime-<up to 168h>`) to hold one IP; one line per unique id = a batch of different sticky IPs.
- The optional filters (`streaming-1`, `direct-1`, `skipispstatic-1`) are best-effort optimizations, not guarantees. Stacking several can lower availability - use only the one you need.
- **Device is best-effort too.** `_device-windows` asks for IPs from that type of device and most come back that way, but not all. It narrows the pool, so combining it with a city, a state or the filters (the speed filter the most) means more failed connections. Only the five listed values are accepted: any other value makes the connection hang with no error, so never pass a free-text value through. Put it after every other flag.

A sticky `session` id (exactly 8 lowercase letters/digits) maps to one exit IP, held on a best-effort basis for the `lifetime` you set (up to 168h). Omit the session for a fresh IP on every new connection; use a **unique id per proxy line** to get a batch of different sticky IPs.

##### Country, state & city lists

Country is an ISO-2 code (lowercase). Cities and US states are the plain name in lowercase with no spaces or separators (`losangeles`, `newyork`). The downloadable reference includes the **full country, city and US-state name lists** (the exact tokens our own dashboard uses) plus all ports and flag formats. Cities are grouped by country (`cities`), US states are in `us_states`, and US cities per state are in `us_state_cities`. A token can still return 503 if that place has no live IP at the moment - just broaden the targeting.

**Premium Residential targeting reference (JSON)**: 196 countries, full city & US-state name lists, all ports, and every flag format. https://catproxies.com/files/premium_residential_x_targeting.json

Tip: any city in the world can be requested, but because the residential pool is dynamic, very small cities may fall back to the wider region. Prefer larger cities. If a request returns no IP, broaden the targeting.

**Example**

```bash
# Credentials from GET /order/:id  (HOST = your order's hostip - a plain
# unbranded IP; per-region HTTP/SOCKS5 ports are in the endpoints object)
#   username: hftjvbtwgxxxceb205604
#   password: ngnstzylne
#   Americas HOST:6011   Europe HOST:6012   Asia HOST:6013

# Worldwide, rotating (bare password = a fresh IP on every new connection)
curl -x HOST:6011 -U "hftjvbtwgxxxceb205604:ngnstzylne" http://ip-api.com/json
# -> {"status":"success","country":"Brazil","city":"Sao Paulo","isp":"Example ISP","query":"203.0.113.5"}

# UK, city London, with the Quality filter (use the Europe endpoint)
curl -x HOST:6012 \
  -U "hftjvbtwgxxxceb205604:ngnstzylne-country-gb_city-london_streaming-1" \
  http://ip-api.com/json
# -> {"status":"success","country":"United Kingdom","city":"London","isp":"Example ISP","query":"198.51.100.7"}

# A DIFFERENT sticky IP per line: give each line its own unique 8-char session
curl -x HOST:6011 -U "hftjvbtwgxxxceb205604:ngnstzylne-country-us_session-a1b2c3d4_lifetime-1h" http://ip-api.com/json
curl -x HOST:6011 -U "hftjvbtwgxxxceb205604:ngnstzylne-country-us_session-e5f6g7h8_lifetime-1h" http://ip-api.com/json

# SOCKS5 works too - just use the region's SOCKS5 port
curl -x socks5h://HOST:6014 -U "hftjvbtwgxxxceb205604:ngnstzylne-country-us" http://ip-api.com/json

# Device targeting (best effort), always the LAST flag
curl -x HOST:6012 -U "hftjvbtwgxxxceb205604:ngnstzylne-country-de_session-a1b2c3d4_lifetime-1h_device-windows" http://ip-api.com/json

# Dedicated ISP pool: endpoint from isp_pools, HTTP only, no country or other targeting
curl -x ISP_POOL_ENDPOINT -U "hftjvbtwgxxxceb205604:ngnstzylne-session-a1b2c3d4_lifetime-1h" http://ip-api.com/json
```

#### Renewing

Extend it exactly like Standard Residential, with no extra body parameters. See Standard Residential, Renewing.

#### Good to know

##### Rotating the password

`POST /order/:id/reset-password` issues a brand-new password for the order (see the Endpoints section). Because targeting flags are appended to the **password**, a reset changes the **base** password only - rebuild your proxy strings with the new base password and re-append the same flags. The old password stops working immediately and the new one takes 1 to 2 minutes to activate.

### Datacenter (`DatacenterP`)

#### What it is

Datacenter plans (`proxyType: DatacenterP`) give you a list of **dedicated IPs** plus a **shared bandwidth pool** for a fixed duration - e.g. 100 IPs / 250 GB / 30 days. At purchase you decide how the IPs split across **US, CA, DE, GB and NL**. Every IP is its own proxy; they all share the same username:password. Each IP works two ways: **static** (that exact IP every time) or **rotating** (a new IP on every request) by adding a `-ttl-0` flag to the username.

#### Buying

**Complete Datacenter workflow**

```bash
# 1. List Datacenter packages - note ips (IP count), bandwidth (GB pool), days
curl "https://catproxies.com/api/v1/public/store?proxyType=DatacenterP" \
  -H "Authorization: Bearer cp_your_api_key_here"

# 2. Check live IP stock per country before allocating
curl "https://catproxies.com/api/v1/public/datacenterp-countries" \
  -H "Authorization: Bearer cp_your_api_key_here"

# 3. Buy - country_proxies must total EXACTLY the package IP count
curl -X POST "https://catproxies.com/api/v1/public/order" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "packageId": "...",
    "datacenterPData": {
      "country_proxies": { "US": 60, "DE": 20, "GB": 20 },
      "high_concurrency": false,
      "high_priority": false,
      "whitelisted_ips": false
    }
  }'
```

| Field | Notes |
| --- | --- |
| `country_proxies` | Must total exactly the package ips value. Allowed countries: US, CA, DE, GB, NL. Check /datacenterp-countries for live stock first - over-allocating a country fails the purchase. |
| `Addons` | high_concurrency (more parallel connections), high_priority (priority routing), whitelisted_ips (3 whitelist slots instead of 1). Each addon enabled adds +25% to the plan price (e.g. all three = +75%). All three are booleans and must be present in datacenterPData. |

#### Proxy format / connecting

```bash
# 1. Read the IP list back (ip_list is refreshed live on every call)
curl "https://catproxies.com/api/v1/public/order/ORDER_ID" \
  -H "Authorization: Bearer cp_your_api_key_here"

# 2. Optional - whitelist your server IP for credential-less access
curl -X PATCH "https://catproxies.com/api/v1/public/order/ORDER_ID/whitelist" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "ip": "203.0.113.42" }'
```

**What GET /order/:id returns for Datacenter**

```json
// All IPs below are documentation examples. Real ones come from the API.
{
  "payload": {
    "order": {
      "id": "c5c7c6b5-e488-420c-b436-e90e86031751",
      "package_title": "Datacenter Proxies - 1 Month - 100 IPs - 250GB",
      "status": "CONFIRMED",
      "proxyCredentials": {
        "username": "dcuser482913",
        "password": "dcpass91827",
        "total_bandwidth": 250,
        "used_bandwidth": 12.4,
        "bandwidth_left": 237.6,
        "available_bandwidth": 237.6,
        "expired_at": "2026-08-04T10:00:00.000Z",
        "port_http": "1338",
        "port_socks5": "1339",
        "whitelist_ip": [],
        "ip_list": [
          {
            "iso2": "US",
            "name": "United States",
            "count_ips": 60,
            "cities": [
              { "name": "New York", "ips": ["203.0.113.24", "203.0.113.57", "..."] }
            ]
          },
          { "iso2": "DE", "name": "Germany", "count_ips": 20, "cities": ["..."] },
          { "iso2": "GB", "name": "United Kingdom", "count_ips": 20, "cities": ["..."] }
        ],
        "country_list": {
          "countries": { "US": 60, "DE": 20, "GB": 20 },
          "total": 100
        },
        // One rotating gateway per country this order has IPs in,
        // picked for THIS order. Read it from here every time; do not save it.
        "rotating_gateways": {
          "US": "198.51.100.10",
          "DE": "198.51.100.40",
          "GB": "198.51.100.50"
        },
        // Every usable gateway per country. The US has two.
        "rotating_gateway_sets": {
          "US": ["198.51.100.10", "198.51.100.20"],
          "DE": ["198.51.100.40"],
          "GB": ["198.51.100.50"]
        },
        "rotating_bound_ip_conflicts": []
      }
    }
  }
}
```

Ports: `1338` HTTP and HTTPS, `1339` SOCKS5.

**Using the proxies**

```bash
# Every IP in ip_list is a dedicated proxy. Same credentials on all of them.
#   HTTP:   <ip>:1338:<username>:<password>
#   SOCKS5: <ip>:1339:<username>:<password>

# STATIC - that exact IP every time
curl -x 203.0.113.24:1338 -U "dcuser482913:dcpass91827" https://ipinfo.io

# ROTATING - a new IP on every new connection: use rotating_gateways.<COUNTRY>
# from GET /order/:id as the host and append -ttl-0 to the USERNAME.
curl -x 198.51.100.10:1338 -U "dcuser482913-ttl-0:dcpass91827" https://ipinfo.io

# After whitelisting your server IP, credentials are optional (static only):
curl -x 203.0.113.24:1338 https://ipinfo.io
```

| Field | Notes |
| --- | --- |
| `Bandwidth` | One shared GB pool across all IPs in the plan. Track it via bandwidth_left on GET /order/:id (refreshed live). |
| `ip_list` | Grouped by country then city. Flatten cities[].ips to get every proxy IP. Refreshed live on each GET /order/:id. |
| `Authentication` | username:password on any IP, or whitelist your server IP (PATCH /order/:id/whitelist) and connect without credentials. |
| `Rotating IPs` | Use rotating_gateways.<COUNTRY> from GET /order/:id as the host and append -ttl-0 to the username for a new IP on every new connection, drawn from that order's own IPs in that country; omit the flag for a fixed IP. The gateway is picked per order, so read it from the API instead of hardcoding it. rotating_gateways only lists the countries this order has IPs in. Do not use one of your own ip_list IPs as the rotating host: with a whitelisted IP, a request through one of the order's own IPs is logged in by IP, the username is ignored and it always exits from that IP. |
| `Rotation granularity` | Per connection, not per request. Open a fresh connection for a new IP. |
| `US rotating` | The US has two gateways and each one only reaches about half of an order's US IPs. rotating_gateway_sets.US lists every usable US gateway for that order (normally two): point your own hostname's A-records at EVERY one of them to rotate through every US IP. |
| `rotating_bound_ip_conflicts` | Countries where the order owns the only rotating gateway. Rotating there keeps giving the same IP while an IP is whitelisted, so remove the whitelisted IP if your customer needs rotating in that country. Static IPs are not affected. |
| `Worldwide rotating` | One blended worldwide line is only possible for plans that include ALL five countries (US, CA, DE, GB, NL). Each gateway only accepts plans that own IPs in its country, so on a plan with fewer countries a worldwide host returns 407 whenever it lands on a country you did not buy. For such plans use one rotating_gateways line per purchased country. |

#### Renewing

```bash
# Renew before the end date: check the price, then renew (empty body)
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/extendOptions" \
  -H "Authorization: Bearer cp_your_api_key_here"

# Renew (empty body)
curl -X POST "https://catproxies.com/api/v1/public/order/ORDER_ID/extend" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'
```

| Field | Notes |
| --- | --- |
| `Renewing` | Supported. GET /order/:id/extendOptions returns one option: this order's own package at your resellerPrice × the add-on multiplier (+25% per add-on bought with the plan), which is exactly what the renewal charges (no VAT). POST /order/:id/extend with {} renews the same plan. Renewing resets your data to the plan's full GB for the new term. Any data you haven't used yet doesn't carry over. Your remaining days do: the new term is added on top of your current end date. One renewal per plan at a time. See Renewing a Datacenter or IPv6 plan below. |

##### Renewing a Datacenter or IPv6 plan

Three calls: check the price, renew, then read the order for the new end date. Datacenter and IPv6 always renew the order's own package, so there is nothing to choose. The price check charges nothing.

```bash
# 1. Check the price. Charges nothing. There is exactly one option:
#    packages[0].price is what step 2 takes from your balance (no VAT),
#    packages[0].note says what the renewal does to the data and days.
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/extendOptions" \
  -H "Authorization: Bearer cp_your_api_key_here"

# 2. Renew. Empty JSON body: the order's own package is used.
curl -X POST "https://catproxies.com/api/v1/public/order/ORDER_ID/extend" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'
# -> payload.extendOrderId, and payload.order with the new
#    proxyCredentials.expired_at already in it

# 3. Read the order again any time: proxyCredentials.expired_at is the
#    end date, bandwidth_left the data left.
curl "https://catproxies.com/api/v1/public/order/ORDER_ID" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

- Renew before the end date. An expired plan cannot be renewed (403 "Cannot extend an expired proxy"); buy a new one instead.
- Datacenter: the price is your resellerPrice × the add-on multiplier (+25% for each add-on the plan was bought with). Renewing resets your data to the plan's full GB for the new term. Any data you haven't used yet doesn't carry over. Your remaining days do: the new term is added on top of your current end date.
- If step 2 times out or you are not sure it landed, do step 3 before trying again. A renewal that went through has moved expired_at. Sending it again would renew, and charge, a second time.
- Two 500 messages mean opposite things. "Failed to extend proxy plan. Your balance has been refunded." means nothing happened: try again later. "Your renewal payment went through, but it needs manual review by our team..." means you WERE charged and our team finishes it within 24 hours or refunds it: do not retry.
- One renewal per order at a time: a call that arrives while one is still running gets 409 and is not charged. A 409 "This plan's last renewal is being checked by our team" means an earlier renewal is under review: contact us, do not renew again.
- Related: POST /order/:id/reset-password rotates the password on both products (the flags live on the username, so only the password half of your proxy strings changes), and GET /order/:id/stats shows how the plan is being used.

#### Usage stats

```bash
# Usage stats: traffic over time, recent connections, top sites
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/stats" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

| Field | Notes |
| --- | --- |
| `Usage analytics` | GET /order/:id/stats: traffic and requests over time, recent connections and top sites. See Usage stats below. |

#### Good to know

| Field | Notes |
| --- | --- |
| `Rotate password` | POST /order/:id/reset-password issues a new 12-character password shared by every IP. It takes a few minutes to switch over on every location, and until then the old one may still work on some. Only the password changes - swap it into all your proxy strings. |
| `HTTP 429` | Returned by the proxy when you hit the plan’s concurrent-connection limit - lower parallelism, or buy with the high_concurrency addon for a higher limit. 407 = wrong credentials / non-whitelisted IP. |

### IPv6 (`Ipv6p`)

#### What it is

IPv6 plans (`proxyType: Ipv6p`) rotate through a huge pool of IPv6 addresses - a fresh exit IP on every request by default. Two plan flavors in /store: **bandwidth plans** (GB pool, no speed cap) and **unlimited plans** (bandwidth "0" = no GB cap, speed capped at the `speed` value in Mbps). Purchase needs only the `packageId` - no extra body fields.

| Field | Notes |
| --- | --- |
| `Bandwidth plans` | bandwidth > 0 in /store. Track usage via bandwidth_left / usage on GET /order/:id (refreshed live). |
| `Unlimited plans` | bandwidth "0" + speed cap in Mbps. usage is null in /order/:id - only expired_at limits the plan. |

#### Proxy format / connecting

##### Connection gateways

IPv6 is served through **country gateways** - the exit country is chosen by which gateway you connect to, and the same username:password works on all of them. Locations: **Worldwide, United States, Germany, United Kingdom, Netherlands**. Ports: `1338` HTTP/HTTPS, `1339` SOCKS5.

The gateway addresses are returned per order by `GET /order/:id` - unbranded plain IPs, safe to pass straight to your own customers:

```bash
curl "https://catproxies.com/api/v1/public/order/ORDER_ID" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

**What GET /order/:id returns for IPv6**

```json
{
  "proxyCredentials": {
    "username": "...",
    "password": "...",
    "hostip": "<worldwide gateway>",   // default gateway
    "port": "1338",
    "ports": { "http": "1338", "socks5": "1339" },
    "endpoints": {                     // one gateway IP per country
      "ww": "...", "us": "...", "de": "...", "gb": "...", "nl": "..."
    },
    "usage": { "bandwidth_left": 87.4, ... }  // null on unlimited plans
  }
}
```

**The target site must support IPv6**

Exits are IPv6-only, so the destination needs an AAAA (IPv6) DNS record. Major platforms (Google, YouTube, Facebook, Instagram, TikTok) support it; many smaller sites do not and will be unreachable. Check your target domain first at [ready.chair6.net](https://ready.chair6.net/), then confirm the proxy works end-to-end by requesting `https://api6.ipify.org` through it. An unreachable IPv6-less target typically surfaces as a `503` from the proxy.

#### Targeting

##### Sessions - append to the username

Default is **rotating**: a new IPv6 address on every request, nothing to configure. For a **sticky session** (keep the same exit IP), append `-session-<id>-ttl-<seconds>` to the **username** (password unchanged). The session id must be a **number between 0 and 999999**; `ttl` is the hold time in **seconds**, up to **86400 (24h)** - e.g. `ttl-1800` = 30 min, `ttl-3600` = 1 hour. Use different session ids to hold several sticky IPs in parallel.

```bash
# Credentials + gateway IPs from GET /order/:id (same login on all gateways)
#   username: ipv6user291   password: v6pass817
#   endpoints.us -> US_GATEWAY,  endpoints.de -> DE_GATEWAY, ...
#   Those gateways are plain unbranded IPs on purpose, so your own customers
#   never see where the plan came from. HTTP 1338, SOCKS5 1339.

# Rotating - fresh IPv6 address on every request (US gateway)
curl -x US_GATEWAY:1338 -U "ipv6user291:v6pass817" https://api6.ipify.org

# Sticky - hold the same IPv6 exit for 60 minutes (ttl is in SECONDS: 60 min = 3600)
curl -x DE_GATEWAY:1338 \
  -U "ipv6user291-session-483920-ttl-3600:v6pass817" \
  https://api6.ipify.org

# SOCKS5
curl --socks5 US_GATEWAY:1339 -U "ipv6user291:v6pass817" https://api6.ipify.org
```

| Field | Notes |
| --- | --- |
| `Sticky session` | -session-<id>-ttl-<seconds> appended to the username. id numeric 0-999999; ttl is the hold time in SECONDS, max 86400 (24h) - e.g. 1800 = 30 min, 3600 = 1h. Omit both for rotating. |

#### Renewing

| Field | Notes |
| --- | --- |
| `Renewing` | Supported. GET /order/:id/extendOptions returns one option: this order's own package at your resellerPrice (no VAT). POST /order/:id/extend with {} renews the same plan. Unlimited plans get the package's days added on top of the current end date (always the plan's own length, for example 30 days on a 30-day plan). Bandwidth plans get its GB added on top of the data left, and the end date moves forward by the plan's own term. A bandwidth plan can hold at most 5000 GB in total: a renewal that would pass that is refused before anything is charged, and extendOptions returns no option. One renewal per plan at a time. See Renewing a Datacenter or IPv6 plan below. |

##### Renewing a Datacenter or IPv6 plan

Three calls: check the price, renew, then read the order for the new end date. Datacenter and IPv6 always renew the order's own package, so there is nothing to choose. The price check charges nothing.

```bash
# 1. Check the price. Charges nothing. There is exactly one option:
#    packages[0].price is what step 2 takes from your balance (no VAT),
#    packages[0].note says what the renewal does to the data and days.
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/extendOptions" \
  -H "Authorization: Bearer cp_your_api_key_here"

# 2. Renew. Empty JSON body: the order's own package is used.
curl -X POST "https://catproxies.com/api/v1/public/order/ORDER_ID/extend" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'
# -> payload.extendOrderId, and payload.order with the new
#    proxyCredentials.expired_at already in it

# 3. Read the order again any time: proxyCredentials.expired_at is the
#    end date, bandwidth_left the data left.
curl "https://catproxies.com/api/v1/public/order/ORDER_ID" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

- Renew before the end date. An expired plan cannot be renewed (403 "Cannot extend an expired proxy"); buy a new one instead.
- IPv6 unlimited plan: the package's days are added on top of the current end date. IPv6 bandwidth plan: the package's GB are added on top of the data left, and the end date moves forward by the plan's own term.
- If step 2 times out or you are not sure it landed, do step 3 before trying again. A renewal that went through has moved expired_at. Sending it again would renew, and charge, a second time.
- Two 500 messages mean opposite things. "Failed to extend proxy plan. Your balance has been refunded." means nothing happened: try again later. "Your renewal payment went through, but it needs manual review by our team..." means you WERE charged and our team finishes it within 24 hours or refunds it: do not retry.
- One renewal per order at a time: a call that arrives while one is still running gets 409 and is not charged. A 409 "This plan's last renewal is being checked by our team" means an earlier renewal is under review: contact us, do not renew again.
- Related: POST /order/:id/reset-password rotates the password on both products (the flags live on the username, so only the password half of your proxy strings changes), and GET /order/:id/stats shows how the plan is being used.

#### Usage stats

| Field | Notes |
| --- | --- |
| `Usage analytics` | GET /order/:id/stats: traffic and requests over time, recent connections and top sites. See Usage stats below. |

#### Good to know

| Field | Notes |
| --- | --- |
| `Authentication` | username:password only. IP whitelisting is currently unavailable for IPv6 plans. |
| `Rotate password` | POST /order/:id/reset-password issues a new 12-character password (old one stops immediately; new one activates in 1-2 minutes). Targeting flags live on the username, so only swap the new password into your proxy strings. |

##### Proxy response codes

| Code | Meaning |
| --- | --- |
| `407` | Wrong or missing proxy credentials. |
| `429` | Concurrent-connection / thread limit reached - lower parallelism or retry with backoff. |
| `503` | Could not reach the target - DNS failed or the site has no IPv6 (AAAA) support. |
| `504` | The target site did not respond in time. |

### Rotating Mobile (`RotatingMobile`)

#### What it is

Rotating Mobile (`proxyType: RotatingMobile`) is a **bandwidth** product on real mobile carriers. Buy and extend it with just the `packageId`. Location and session targeting are **appended to the username**. Rotating IP per request by default.

#### Proxy format / connecting

Use the host and port from `GET /order/:id` (`proxyCredentials.hostip` / `port`, HTTP port `5000`). The same credentials work for every location; you choose the location by the username flags below.

#### Targeting

| Target | Flag | Value format |
| --- | --- | --- |
| Country | `-country-US` | ISO-2 code |
| Region / state | `-region-california` | region code from /regions |
| City | `-city-los+angeles` | city code from /cities. Spaces are a PLUS sign. |
| Carrier / ISP | `-isp-at&t+wireless` | isp code from /isps |
| Sticky session | `-sid-ab12-ttl-30m` | any id + ttl (see below): one of 1m 5m 10m 30m 60m 120m 180m 240m |

For a sticky IP add `-sid-<id>-ttl-<duration>`, where duration is one of `1m 5m 10m 30m 60m 120m 180m 240m` (hold up to 4h). Omit the sticky flags for a fresh IP each request. All of `region`, `city`, `isp` and the sticky flags are optional.

**Value format: read this before targeting**

- Use the codes exactly as returned by the targeting endpoints. **Spaces are written as `+`, never as `-`**, e.g. `los+angeles`, `new+york`, `at&t+wireless`. Regions are full names (`california`), not 2-letter codes.
- An unknown or malformed value **fails with 502**, and it does not silently fall back to untargeted rotation. If you get a 502 on a targeted request, check the code first.
- Carrier availability varies by city, so a valid city + carrier pair can still return 502 if that carrier has no live IPs there right now. **For reliable carrier targeting, pin the carrier at country level** and omit the city flag. City targeting is metro-approximate, not pinpoint.
- Untargeted rotation is not evenly weighted across carriers. If you need a specific carrier mix, request each carrier explicitly rather than relying on the default rotation.

##### Discovering locations

The valid country / region / city / ISP codes come from the **Mobile Targeting endpoints** (`/mobile-targeting-options/*`). Those endpoints share a limit of 60 requests per 10 minutes - cache them (the lists rarely change), or download the full offline snapshot from the Targeting section for bulk setup. For a static starting point you can also reuse the city and US-state name lists in the **Premium Residential targeting reference** - the same place names apply, though the exact set valid for mobile comes from the endpoints above.

**Example**

```bash
# Credentials from GET /order/:id
#   username: mobileuser71   password: mob9182pass
#   host: HOST = proxyCredentials.hostip from GET /order/:id, a plain unbranded
#         IP on purpose so your own customers never see where the plan came
#         from.  port: 5000

# Rotating mobile IP in the US (new IP per request)
curl -x HOST:5000 \
  -U "mobileuser71-country-US:mob9182pass" \
  https://ipinfo.io

# Rotating IP on AT&T anywhere in the US
curl -x HOST:5000 \
  -U "mobileuser71-country-US-isp-at&t+wireless:mob9182pass" \
  https://ipinfo.io

# Sticky IP on AT&T in California for 30 minutes
curl -x HOST:5000 \
  -U "mobileuser71-country-US-region-california-isp-at&t+wireless-sid-ab12-ttl-30m:mob9182pass" \
  https://ipinfo.io
```

#### Renewing

Extend it with just the `packageId`, the same way you buy it.

#### Good to know

##### Resetting the login

`POST /order/:id/reset-password` gives the plan a **whole new login** and moves its remaining data onto it, so the **username and the password both change**. The response carries both (`username` and `password`); rebuild your proxy strings from them and re-append your targeting flags to the new username. The old login stops working straight away, and the plan's used and total GB carry over. Allowed once per hour per plan, not within 5 minutes of a top-up, and not on a plan with no data left.

### Usage stats

Datacenter and IPv6 orders come with usage analytics: traffic and requests over time, a log of recent connections, and the sites used most. It is one read-only endpoint, GET /order/:id/stats, and the view parameter picks which of the three you get. It never changes the plan, so you can pass the figures straight on to your own customers.

```bash
# Traffic over the last 24 hours, one row per hour (the defaults)
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/stats" \
  -H "Authorization: Bearer cp_your_api_key_here"

# The last week, one row per day
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/stats?timeframe=1week&interval=1day" \
  -H "Authorization: Bearer cp_your_api_key_here"

# Recent connections, 100 per page
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/stats?view=activity&page=1" \
  -H "Authorization: Bearer cp_your_api_key_here"

# The sites used most this month
curl "https://catproxies.com/api/v1/public/order/ORDER_ID/stats?view=top-domains&timeframe=1month" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

- timeframe: 1hour, 6hours, 1day (default), 1week, 1month or 3months. interval, for the overview only: 5min, 30min, 1hour (default), 6hour, 1day, 1week or 1month. An unknown value quietly falls back to the default, so check timeframe and interval in the reply.
- available: false means the stats source could not be reached just now and the lists are empty. Retry later; it does not mean zero traffic.
- The figures lag a little behind real time. For what is left on the plan right now, read proxyCredentials.bandwidth_left on GET /order/:id instead.
- Units: the overview is in GB, activity and top-domains traffic is in bytes, and success_rate is a percentage. Timestamps are "YYYY-MM-DD HH:MM:SS" strings.
- GET /order/:id/unlimited-metrics is the older name of the same endpoint and returns exactly the same thing.

## Endpoints

All paths are relative to `https://catproxies.com/api/v1/public`. Responses are JSON shaped as `{ type, message, payload, pagination? }`.

### Account

#### `GET /account`

Get your account balance, email, total orders, and rate limit information.

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `balance` | number | Current account balance in USD |
| `email` | string | Account email address |
| `displayName` | string | Account display name |
| `totalOrders` | number | Total number of confirmed orders |
| `rateLimits.requestsPerHour` | number | Maximum requests allowed per hour (1000) |
| `rateLimits.requestsUsed` | number | Number of requests made in current hour |
| `rateLimits.requestsRemaining` | number | Remaining requests in current hour |
| `rateLimits.resetAt` | string | When the rate limit counter resets (ISO 8601) |

Request:

```bash
curl -X GET https://catproxies.com/api/v1/public/account \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"balance": 187.05,
"email": "reseller@example.com",
"displayName": "John Doe",
"totalOrders": 42,
"rateLimits": {
  "requestsPerHour": 1000,
  "requestsUsed": 7,
  "requestsRemaining": 993,
  "resetAt": "2025-11-29T20:00:00.000Z"
}
  },
  "pagination": {}
}
```

#### `GET /billing`

Your billing statement, newest first: the same list as the Billing page in the reseller panel. Every purchase, renewal, GB added to a plan (POST /order/:id/bandwidth), deposit and refund on the account.

Read-only, charges nothing. direction "out" means money left your balance, "in" means it came back or was deposited. reference is the order ID the row belongs to (or the renewal ID when referenceType is "extend").

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | number | no | Page number |
| `pageSize` | number | no | Items per page (max 100) |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `kind` | string | "purchase", "extend" (a renewal or GB added to a plan), "deposit" or "refund". |
| `label` | string | What the row is, e.g. "Standard Residential V2 5GB", "Added 10 GB", "Extend 30 days". |
| `proxyType` | string | null | Product of the plan the row belongs to (null on a plain deposit). |
| `amount` | number | Amount in USD, always positive. Read direction for the sign. |
| `direction` | string | "out" = taken from your balance, "in" = added to it. |
| `status` | string | CONFIRMED, CANCELED, PENDING, etc. A CANCELED purchase is one that was deleted or failed; any money returned for it appears as its own "in" row. |
| `actionBy` | string | "User" (you), "Admin" or "AutoSystem" (an automatic refund or adjustment). |
| `referenceType / reference` | string | "order" + the order ID, or "extend" + the renewal ID. Empty on a plain deposit. |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/billing?page=1&pageSize=20" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"transactions": [
  {
    "id": "c_283c37e6-8efe-40df-83b8-dcb25ff4edfa",
    "kind": "extend",
    "label": "Added 10 GB",
    "proxyType": "v2resi",
    "amount": 7,
    "direction": "out",
    "status": "CONFIRMED",
    "paymentMethod": "Balance",
    "actionBy": "User",
    "createdAt": "2026-10-09T02:57:36.692Z",
    "referenceType": "order",
    "reference": "700c853f-9818-4162-bed9-10003d3cb85e"
  }
]
  },
  "pagination": {
"page": 1,
"pageSize": 20,
"total": 57,
"totalPages": 3,
"hasMore": true
  }
}
```

### Store & Products

#### `GET /store`

Get all available proxy packages with your custom reseller pricing. Custom ISP and Dedicated ISP packages are priced per IP.

Custom ISP packages (proxyType Isp) carry a customIsp object with the tier, the plan length and the add-on prices. One package is one tier and one length, and resellerPrice is your price per IP for it. Their locations[] is empty: the cities come from GET /custom-isp/catalog, which is also the easier way to list your Custom ISP prices.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | number | no | Page number (starts at 1) |
| `pageSize` | number | no | Items per page (max 100) |
| `proxyType` | string | no | Filter by proxy type. Use the proxyType value returned for each product in /store (products[].proxyType). |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `products[].packageId` | string | Package ID for purchasing |
| `products[].title` | string | Product title/name |
| `products[].originalPrice` | number | Original retail price in USD. For ISP this is the per-IP price. |
| `products[].resellerPrice` | number | Your reseller price in USD. For ISP this is the per-IP price - multiply by quantity for total. |
| `products[].discount` | number | Discount percentage vs original price |
| `products[].proxyType` | string | Proxy type. Exact values to filter by: v2resi (Standard Residential), resix (Premium Residential), Isp (Custom ISP), IspP (Dedicated ISP), DatacenterP (Datacenter), Ipv6p (IPv6), RotatingMobile. See the Product types table in the Overview. |
| `products[].bandwidth` | string|null | Bandwidth in GB. null for ISP packages. "0" on IPv6 = unlimited bandwidth (plan is speed-capped instead - see speed). |
| `products[].ips` | number|null | Datacenter (DatacenterP) only - number of dedicated IPs in the package. Your country allocation at purchase must total this number. |
| `products[].speed` | number|null | Speed cap in Mbps (e.g. 30, 100, 1000). Set on speed-based plans - IPv6 unlimited, where it identifies the plan tier. 0 or null = no speed cap. |
| `products[].days` | number | Package duration in days |
| `products[].pricingNote` | string|null | Custom ISP and Dedicated ISP - human-readable pricing summary e.g. "$2.50/IP × 1-10 IPs" |
| `products[].minQty` | number|null | Custom ISP and Dedicated ISP - minimum quantity (IPs) per order |
| `products[].maxQty` | number|null | Custom ISP and Dedicated ISP - maximum quantity (IPs) per order. null means unlimited. |
| `products[].isSubnet` | boolean | Dedicated ISP only - a /24 subnet block. Subnet plans are NOT buyable via the API (they 400 on purchase); arrange them with support. |
| `products[].locations` | array | Always an empty array. Custom ISP cities come from GET /custom-isp/catalog. Dedicated ISP is pool-based, with no country to pick. |
| `products[].customIsp` | object|null | Custom ISP only, null on every other product. { tier, tierLabel, length, carrierFeePerIp, replacementPrice, maxReplacements }. tier is "standard" or "premium", tierLabel is "Standard" or "Clean Score". length is "1_day", "7_days", "1_month", "2_months" or "3_months". carrierFeePerIp is added per IP when the order picks a carrier, replacementPrice is the price of one IP replacement bought with the plan, maxReplacements is 5. |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/store?page=1&pageSize=20&proxyType=Isp" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"products": [
  {
    "packageId": "a1b2c3d4-0003-4a5b-8c9d-0e1f2a3b4c5d",
    "title": "Custom ISP Standard - 1 Month",
    "description": "ISP proxies, city and carrier of your choice",
    "originalPrice": 4,
    "resellerPrice": 3.2,
    "discount": 20,
    "savings": 0.8,
    "proxyType": "Isp",
    "bandwidth": null,
    "ips": null,
    "speed": null,
    "days": 30,
    "hidden": false,
    "pricingNote": "$3.2/IP × 1-100 IPs",
    "minQty": 1,
    "maxQty": 100,
    "locations": [],
    "customIsp": {
      "tier": "standard",
      "tierLabel": "Standard",
      "length": "1_month",
      "carrierFeePerIp": 0.35,
      "replacementPrice": 1.1,
      "maxReplacements": 5
    }
  }
],
"totalProducts": 1
  },
  "pagination": {
"page": 1,
"pageSize": 20,
"totalPages": 1,
"hasMore": false
  }
}
```

#### `GET /custom-isp/catalog`

Everything you need to build a Custom ISP order. Custom ISP is static ISP proxies where the buyer picks the tier, the plan length, the city and, optionally, the carrier and UDP support.

plans lists one package per tier and length that has a reseller price on your account. pricePerIp is YOUR price per IP; only the tiers and lengths you were given a price for are listed. Send the packageId to POST /order. countries lists the cities with IPs in stock right now for the tier and UDP choice you asked for, with how many IPs each has. Send a city's location string exactly as returned.

The tiers are standard and premium. premium is sold as Clean Score: the IPs are checked right before delivery, with a fraud score of 0 when delivered. Checking an IP on a score site through the proxy raises its score, so check it from a connection that is not using the proxy.

The IP counts come from a cache that is at most a minute old. POST /order checks stock again before charging. When Custom ISP is not on sale (switched off, or new reseller purchases are paused) the payload is just { "enabled": false }. When you have no plan in the tier you asked for, countries is empty. Limited to 40 requests per minute per account.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `tier` | string | no | "standard" or "premium" (Clean Score). Picks which tier's cities and IP counts are listed. plans always holds every tier you have a price for. |
| `udp` | string | no | "1" to count only IPs with UDP support. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `enabled` | boolean | false when Custom ISP is not on sale right now. Nothing else is sent then. |
| `tiers` | array | Tiers you have at least one plan in. |
| `plans[tier][length].packageId` | string | Send as packageId to POST /order. |
| `plans[tier][length].pricePerIp` | number | Your reseller price for one IP for this length. |
| `plans[tier][length].days` | number | Plan length in days. |
| `plans[tier][length].minQty / maxQty` | number | Allowed IPs per order. |
| `plans[tier][length].replacementPrice` | number|null | Price of one IP replacement bought with this plan. null when replacements are not sold on it. |
| `carrierFeePerIp` | number | Added per IP when the order picks a carrier (ispData.asn). |
| `maxReplacements` | number | Most IP replacements one order can be bought with (5). |
| `countries[].cities[].location` | string | "Country - City". Send exactly this as ispData.location and to GET /custom-isp/carriers. |
| `countries[].cities[].count` | number | IPs in stock in that city for this tier and UDP choice. |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/custom-isp/catalog?tier=standard&udp=0" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"enabled": true,
"tiers": ["standard", "premium"],
"plans": {
  "standard": {
    "1_month": { "packageId": "a1b2c3d4-0003-4a5b-8c9d-0e1f2a3b4c5d", "pricePerIp": 3.2, "days": 30, "minQty": 1, "maxQty": 100, "replacementPrice": 1.1 },
    "3_months": { "packageId": "a1b2c3d4-0005-4a5b-8c9d-0e1f2a3b4c5d", "pricePerIp": 9, "days": 90, "minQty": 1, "maxQty": 100, "replacementPrice": 1.1 }
  },
  "premium": {
    "1_month": { "packageId": "a1b2c3d4-0103-4a5b-8c9d-0e1f2a3b4c5d", "pricePerIp": 4.5, "days": 30, "minQty": 1, "maxQty": 100, "replacementPrice": 1.1 }
  }
},
"carrierFeePerIp": 0.35,
"maxReplacements": 5,
"countries": [
  {
    "country": "United States",
    "total": 1840,
    "cities": [
      { "city": "New York City", "location": "United States - New York City", "count": 412 },
      { "city": "Ashburn", "location": "United States - Ashburn", "count": 298 }
    ]
  }
]
  },
  "pagination": {}
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 503 | Live stock is unavailable right now. Please try again in a minute. | Stock could not be read just now. Try again shortly. |
| 429 | Too many requests. Please wait a moment. | More than 40 calls in a minute. Cache the result for a minute. |

#### `GET /custom-isp/carriers`

The carriers (ASNs) with IPs in stock in one city, best first, for one tier and UDP choice. Picking a carrier is optional. To pin an order to one, send its asn as ispData.asn on POST /order; every IP then costs carrierFeePerIp more.

location must be a city exactly as GET /custom-isp/catalog returns it. Use the same tier and udp you will buy. Cached for about two minutes. Limited to 40 requests per minute per account.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `location` | string | yes | A city from the catalog, e.g. "United States - New York City". |
| `tier` | string | no | "standard" or "premium". The tier you will buy. |
| `udp` | string | no | "1" for UDP-capable IPs only. The same choice you will buy. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `carrierFeePerIp` | number | Added per IP when the order picks one of these carriers. |
| `carriers[].asn` | number | Send as ispData.asn on POST /order. |
| `carriers[].name` | string | Carrier name. |
| `carriers[].stock` | number | IPs on that carrier in stock in this city. |

Request:

```bash
curl -G "https://catproxies.com/api/v1/public/custom-isp/carriers" \
  --data-urlencode "location=United States - New York City" \
  --data-urlencode "tier=standard" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"carrierFeePerIp": 0.35,
"carriers": [
  { "asn": 7922, "name": "Comcast Cable Communications", "stock": 96 },
  { "asn": 701, "name": "Verizon Business", "stock": 41 }
]
  },
  "pagination": {}
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 400 | location is required (a city from GET /custom-isp/catalog). | location is missing. |
| 400 | This city is not available. | location is not a city from the catalog, or it has no stock for this tier and UDP choice. |
| 503 | Carrier stock is unavailable right now. Please try again in a minute. | Stock could not be read just now. Try again shortly. |
| 429 | Too many requests. Please wait a moment. | More than 40 calls in a minute. |

#### `POST /custom-isp/domain`

Sets up your own hostname for Custom ISP lines, so your customers get your name, with the same Auto option we use. Send a subdomain of a domain you own (e.g. isp.yourbrand.com, one per account). The reply lists 2 NS records: add them at your DNS provider for that name (and no A or CNAME records). Until they are seen, status is "pending" and your lines keep using plain server IPs; GET /custom-isp/domain switches it to "active" once the records are in place. Then {domain} is Auto (the closest server to whoever connects) and <location>.{domain} is one location, and dead servers are taken out automatically.

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `domain` | string | yes | A subdomain of a domain you own, at least 3 parts, e.g. isp.yourbrand.com. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `domain` | string|null | Your hostname, or null when none is set. |
| `status` | string | "none", "pending" (waiting for your NS records; lines use plain server IPs) or "active" (lines use your hostname). |
| `records` | array | The 2 NS records to add at your DNS provider: { type, name, value }. |
| `hosts` | object | auto plus one name per location (us-east, us-central, us-west, ca, uk, nl, de, fi, sg, jp, au). Pass the id as ?region= on GET /order/:id/proxies. |

Request:

```bash
curl -X POST "https://catproxies.com/api/v1/public/custom-isp/domain" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "isp.yourbrand.com" }'
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
    "domain": "isp.yourbrand.com",
    "status": "pending",
    "nameservers": ["kiki.bunny.net", "coco.bunny.net"],
    "records": [
      { "type": "NS", "name": "isp.yourbrand.com", "value": "kiki.bunny.net" },
      { "type": "NS", "name": "isp.yourbrand.com", "value": "coco.bunny.net" }
    ],
    "hosts": {
      "auto": "isp.yourbrand.com",
      "us-east": "us-east.isp.yourbrand.com",
      "de": "de.isp.yourbrand.com"
    },
    "activeAt": null
  }
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 400 | Please use a subdomain of a domain you own, e.g. isp.yourbrand.com. | Not a valid subdomain. |
| 409 | You already have a hostname set. Remove it first to use a different one. | One hostname per account. DELETE it first. |
| 409 | This hostname is already in use. | Another account uses it. |

#### `GET /custom-isp/domain`

Your hostname, its status and the names your lines use. While the status is "pending" every call also checks your NS records (at most every 20 seconds) and switches it to "active" when they are in place. DNS changes can take up to an hour.

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `domain` | string|null | Your hostname, or null when none is set. |
| `status` | string | "none", "pending" (waiting for your NS records; lines use plain server IPs) or "active" (lines use your hostname). |
| `records` | array | The 2 NS records to add at your DNS provider: { type, name, value }. |
| `hosts` | object | auto plus one name per location (us-east, us-central, us-west, ca, uk, nl, de, fi, sg, jp, au). Pass the id as ?region= on GET /order/:id/proxies. |

Request:

```bash
curl "https://catproxies.com/api/v1/public/custom-isp/domain" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
    "domain": "isp.yourbrand.com",
    "status": "active",
    "nameservers": ["kiki.bunny.net", "coco.bunny.net"],
    "records": [
      { "type": "NS", "name": "isp.yourbrand.com", "value": "kiki.bunny.net" },
      { "type": "NS", "name": "isp.yourbrand.com", "value": "coco.bunny.net" }
    ],
    "hosts": {
      "auto": "isp.yourbrand.com",
      "us-east": "us-east.isp.yourbrand.com",
      "de": "de.isp.yourbrand.com"
    },
    "activeAt": "2026-10-14T09:12:00.000Z"
  }
}
```

#### `DELETE /custom-isp/domain`

Removes your hostname. Your Custom ISP lines go back to plain server IPs right away. Remove the NS records on your side too.

Request:

```bash
curl -X DELETE "https://catproxies.com/api/v1/public/custom-isp/domain" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": { "removed": true }
}
```

#### `GET /datacenterp-countries`

Get available countries and IP stock for DatacenterP proxies. Use this to check which countries are available and how many IPs can be allocated before purchasing.

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `countries` | array | Array of available countries with stock |
| `countries[].code` | string | ISO country code (use in datacenterPData.country_proxies) |
| `countries[].name` | string | Country name |
| `countries[].available` | number | Number of IPs currently available |
| `allowedCountryCodes` | array | List of valid country codes for DatacenterP |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/datacenterp-countries" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"countries": [
  { "code": "US", "name": "United States", "available": 4832 },
  { "code": "CA", "name": "Canada", "available": 1205 },
  { "code": "DE", "name": "Germany", "available": 3410 },
  { "code": "GB", "name": "United Kingdom", "available": 2108 },
  { "code": "NL", "name": "Netherlands", "available": 1567 }
],
"allowedCountryCodes": ["US", "CA", "DE", "GB", "NL"]
  }
}
```

### Orders

#### `GET /orders`

Get all your reseller orders with pagination, search, and sorting capabilities.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | number | no | Page number |
| `pageSize` | number | no | Items per page (max 100) |
| `search` | string | no | Search by order ID |
| `orderBy` | string | no | Field to sort by |
| `order` | string | no | Sort order (asc/desc) |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `orders[].id` | string | Order ID |
| `orders[].package_title` | string | Package title |
| `orders[].status` | string | Order status (CONFIRMED, PENDING) |
| `orders[].amount` | number | Amount paid (excluding tax) |
| `orders[].createdAt` | string | When order was created (ISO 8601) |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/orders?page=1&pageSize=10" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"orders": [
  {
    "id": "700c853f-9818-4162-bed9-10003d3cb85e",
    "package_title": "Standard Residential 2GB",
    "package_bandwidth": 2,
    "package_days": 90,
    "package_proxyType": "v2resi",
    "status": "CONFIRMED",
    "amount": 2.23,
    "createdAt": "2025-11-13T12:05:09.820Z"
  }
]
  },
  "pagination": {
"page": 1,
"pageSize": 10,
"totalOrders": 42,
"totalPages": 5,
"hasMore": true
  }
}
```

#### `POST /order`

Create a new proxy purchase order using your reseller balance. Returns full order details including proxy credentials.

For Dedicated ISP (IspP) proxies, pass ispData with just the quantity - these are pool-based, so there is NO country (locations is empty). Total = resellerPrice × quantity, within the package minQty to maxQty range. The term is a fixed 30 days. The individual IP:port lines are fetched from GET /order/:id/proxies (not on GET /order/:id).

For Custom ISP proxies (proxyType Isp, packageId from GET /custom-isp/catalog), pass ispData with { quantity, location, asn?, udp?, replacements? }. quantity and location are required. You are charged from your balance: your price per IP x quantity, plus carrierFeePerIp x quantity when you set asn, plus replacements x replacementPrice. No VAT. Live stock is checked before you are charged, and a city or carrier that ran out is refused with nothing charged. If delivery is refused or the stock is gone at delivery, the balance is refunded automatically. If the result is unclear, the order is held for manual review and delivered within 24 hours or refunded: do not order again. The lines come from GET /order/:id/proxies.

For Datacenter (DatacenterP) proxies, you must include datacenterPData with country allocation and addon configuration.

For IPv6 (Ipv6p) proxies, no extra fields are needed - just the packageId.

Optional customer tagging: you can add customerId (a string: your own id or label for the end customer) to group that customer's plans together in your panel. It is completely optional and changes nothing about the purchase; leave it out and orders behave exactly as before. You can also tag or re-tag an existing order later with POST /order/:id/customer.

IMPORTANT (Dedicated ISP and Custom ISP): on the rare occasion an order is charged but needs manual review, you get an error whose message says the order "needs manual review... will be delivered within 24 hours or refunded". READ THE MESSAGE, NOT ONLY THE STATUS: this case currently comes back as HTTP 500, so a blanket "retry on 5xx" wrapper will buy the plan a SECOND time and charge you again. The charge already stands and a human is finishing the order. Never auto-retry POST /order at all: it is not idempotent and there is no idempotency key, so every retry is a new order and a new charge. If a request times out, look the order up with GET /orders (tag it with customerId when you buy and you can match it) instead of sending it again. Renewals (POST /order/:id/extend) are not idempotent either: a second call after the first one succeeded renews, and charges, a second time. Only a call that arrives while a Datacenter, IPv6 or Custom ISP renewal of the same order is still running is refused (409, not charged). If a renewal times out, read GET /order/:id and compare expired_at before sending it again.

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `packageId` | string | yes | Package ID from /store endpoint |
| `customerId` | string | no | OPTIONAL. Your own id or label for the end customer this plan is for. Passing it groups that customer's plans together in your panel. Purely for your own organisation, it changes nothing about the order, the price or the proxy. Omit it and everything works exactly as before. |
| `ispData` | object | no | Custom ISP and Dedicated ISP only. The fields below. |
| `ispData.quantity` | integer | no | Number of IPs to purchase. Must be within the package minQty to maxQty range. Default: 1 (required on Custom ISP). Charged: resellerPrice × quantity (on Custom ISP plus the carrier fee and replacements). |
| `ispData.location` | string | no | Custom ISP only, and required there. The city exactly as GET /custom-isp/catalog returns it, e.g. "United States - New York City". |
| `ispData.asn` | number | no | Custom ISP only. A carrier from GET /custom-isp/carriers for that city. Adds carrierFeePerIp per IP. Omit for any carrier. |
| `ispData.udp` | boolean | no | Custom ISP only. true = only IPs with UDP support. Default false. |
| `ispData.replacements` | integer | no | Custom ISP only. IP replacements bought with the plan, 0 to 5, each at the plan's replacementPrice. Used with POST /order/:id/replace. Not renewed. Default 0. IP replacements can only be bought together with the order: they cannot be added later, so choose how many you need when you buy. |
| `datacenterPData` | object | no | Required for DatacenterP proxies. Contains country allocation and addon configuration. |
| `datacenterPData.country_proxies` | object | no | Country code → IP count mapping. Allowed: US, CA, DE, GB, NL. Total must equal package IP count. A single worldwide-rotating line is only available when ALL five countries are included; otherwise rotate per country via rotating_gateways. |
| `datacenterPData.high_concurrency` | boolean | no | Enable high concurrency addon (+25% price) |
| `datacenterPData.high_priority` | boolean | no | Enable high priority addon (+25% price) |
| `datacenterPData.whitelisted_ips` | boolean | no | Enable extra whitelisted IPs addon (+25% price) |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `order.id` | string | Created order ID |
| `order.status` | string | Order status (CONFIRMED) |
| `proxyCredentials.username` | string | Proxy authentication username |
| `proxyCredentials.password` | string | Proxy authentication password |
| `proxyCredentials.hostip` | string | Proxy hostname or IP |
| `proxyCredentials.port` | string | The DEFAULT port, as a string. On Standard Residential it is a RANGE, e.g. "9000-12000", not a single port: do not concatenate it blindly into host:port. Read the exact ports from proxyCredentials.endpoints / ports, or from the per-product guide below (Standard Residential 9000 rotating, 10000 sticky, 11000/12000 SOCKS5). |

Request:

```bash
# Standard purchase
curl -X POST "https://catproxies.com/api/v1/public/order" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "6f19b4e7-1b1e-4ed5-85e4-41e6bbe07101" }'

# Standard purchase - optionally tagged to one of your customers
curl -X POST "https://catproxies.com/api/v1/public/order" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "6f19b4e7-1b1e-4ed5-85e4-41e6bbe07101", "customerId": "acme-co" }'

# Custom ISP - 5 IPs in New York City on one carrier, with 2 IP replacements
# (packageId and location from GET /custom-isp/catalog, asn from /custom-isp/carriers)
# Charged: 3.20 x 5 + 0.35 x 5 + 1.10 x 2 = 19.95
curl -X POST "https://catproxies.com/api/v1/public/order" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
"packageId": "a1b2c3d4-0003-4a5b-8c9d-0e1f2a3b4c5d",
"ispData": { "quantity": 5, "location": "United States - New York City", "asn": 7922, "udp": false, "replacements": 2 }
  }'

# DatacenterP (country allocation must total the package IP count):
curl -X POST "https://catproxies.com/api/v1/public/order" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
"packageId": "pkg-datacenterp-100",
"datacenterPData": {
  "country_proxies": { "US": 60, "DE": 20, "GB": 20 },
  "high_concurrency": true,
  "high_priority": false,
  "whitelisted_ips": false
}
  }'
```

Response:

```json
{
  "type": "response",
  "message": "Purchase successful! Your proxy has been activated.",
  "payload": {
"order": {
  "id": "8a08caa7-e1f2-487e-aedb-9ebc018ab8f0",
  "package_title": "Residential 1GB",
  "status": "CONFIRMED",
  "amount": 2.23,
  "proxyCredentials": {
    "username": "hftjvbtwgxxxceb205604",
    "password": "ngnstzylne",
    "hostip": "203.0.113.10",
    "port": "9000-12000",
    "available_bandwidth": 1,
    "bandwidth_left": 1,
    "expired_at": "2026-02-27T20:46:02.383Z"
  }
}
  }
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 400 | We're temporarily out of stock for this plan. Your payment went through and your order has been flagged to our team. It will be delivered manually within 24 hours, or you'll be refunded. No action is needed from you. | Dedicated ISP. The payment is kept and we retry automatically for up to 3 hours, then refund your balance if it still cannot be delivered. Do not order again. |
| 500 | Your payment went through, but this order needs manual attention from our team. It has been flagged automatically and will be delivered within 24 hours, or you'll receive a refund. Please don't re-order. Contact us if you'd like an update. | Dedicated ISP, unclear result. You were charged and our team delivers or refunds the order. Do not order again. |
| 400 | Insufficient balance | Top up and try again. Nothing was charged. |
| 400 | ispData with location and quantity is required for Custom ISP | Custom ISP: send ispData.location (a city from GET /custom-isp/catalog) and ispData.quantity. |
| 400 | Quantity must be between 1 and 100. | The quantity is outside the plan's minQty and maxQty. Nothing was charged. |
| 400 | This city is out of stock right now. Please pick another one. | Custom ISP: the city ran out, or location is not a city from the catalog. Nothing was charged. |
| 400 | This carrier is out of stock in this city right now. Please pick another one. | Custom ISP: no IPs left on that asn in that city. Nothing was charged. |
| 400 | Only 3 IPs are available for this choice right now. Please lower the quantity or change your filters. | Custom ISP: fewer IPs in stock than you asked for. Nothing was charged. |
| 400 | You can add up to 5 IP replacements. | Custom ISP: replacements is above 5. Nothing was charged. |
| 403 | This product is not available at the moment. Please try again later. | New reseller purchases of this product or plan are paused, or the plan is not in your store. Nothing was charged. |
| 503 | Live stock is unavailable right now. Please try again in a minute. | Custom ISP: we could not check stock just now. Nothing was charged; try again shortly. |
| 500 | We could not activate this proxy right now, so nothing was charged - your balance has been refunded. Please try again in a few minutes. | Delivery failed and your balance is back. Safe to buy again later. |
| 500 | Your payment went through, but this order needs manual review by our team. It will be delivered within 24 hours or refunded. Please don't re-order. | You WERE charged and our team finishes the order. Do NOT retry: a retry is a new order and a new charge. |

#### `GET /order/:id`

Get detailed information about a specific order including proxy connection details, usage figures, and whitelisted IPs. This is also where you read the new end date (proxyCredentials.expired_at) after a renewal.

The fields under proxyCredentials depend on the product. Datacenter and IPv6 are shown below.

Datacenter: every IP in ip_list is a dedicated proxy on port_http (1338) and port_socks5 (1339). For ROTATING (a new IP on every new connection), connect to rotating_gateways.<COUNTRY> and append -ttl-0 to the username. The rotating gateways are picked per order, so read them from here each time instead of saving them. rotating_gateways and rotating_gateway_sets only list the countries this order has IPs in (a gateway for any other country would not accept the order). rotating_gateway_sets lists every usable gateway per country. The US has two gateways and each one reaches about half of the order's US IPs, so to give your customer your own hostname for US rotating, point its A-records at EVERY IP in rotating_gateway_sets.US (normally two). rotating_bound_ip_conflicts lists the countries where the order owns the only gateway: rotating there keeps giving the same IP while an IP is whitelisted.

IPv6: the exit country is picked by the gateway you connect to (endpoints: ww, us, de, gb, nl), on ports.http (1338) or ports.socks5 (1339). usage is null on unlimited (speed-capped) plans.

Custom ISP: proxyCredentials has the order's one username and password, port "1337" (HTTP and SOCKS5 on the same port), auth_mode "logpas" and a note. The per-IP lines come from GET /order/:id/proxies, and POST /order/:id/replace swaps an IP. package_region holds what was bought, as a JSON string: { kind: "customisp", tier, location, asn, udp, length, replacements, carrierName }.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | The order ID |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `order.id` | string | Order ID |
| `order.status` | string | Order status |
| `proxyCredentials.username` | string | Proxy username |
| `proxyCredentials.password` | string | Proxy password |
| `proxyCredentials.total_bandwidth` | number|null | Total bandwidth on the plan in GB (used_bandwidth + bandwidth_left). null on unlimited / speed-capped plans that have no GB cap. |
| `proxyCredentials.used_bandwidth` | number | Bandwidth used so far, in GB. |
| `proxyCredentials.bandwidth_left` | number | Remaining bandwidth in GB. |
| `proxyCredentials.available_bandwidth` | number | Remaining bandwidth in GB. Legacy alias of bandwidth_left, kept for backward compatibility. Prefer bandwidth_left and total_bandwidth. |
| `proxyCredentials.expired_at` | string | End date of the plan (ISO 8601). Read it again after a renewal to see the new end date. |
| `proxyCredentials.note` | string | Datacenter, IPv6 and some other products: plain-text connection help for the product. Informational only, do not parse it. |
| `proxyCredentials.ip_list` | array | Datacenter only. The order's IPs grouped by country, then city: [{ iso2, name, count_ips, cities: [{ name, ips: [] }] }]. Flatten cities[].ips for every address. Refreshed live on each call. |
| `proxyCredentials.country_list` | object|null | Datacenter only. { countries: { "US": 60, ... }, total }: how many IPs the order has per country, and in total. |
| `proxyCredentials.port_http` | string | Datacenter only. "1338", the HTTP/HTTPS port on every IP and rotating gateway. |
| `proxyCredentials.port_socks5` | string | Datacenter only. "1339", the SOCKS5 port on every IP and rotating gateway. |
| `proxyCredentials.whitelist_ip` | array | Datacenter: the IPs whitelisted for credential-less access (see /order/:id/whitelist). |
| `proxyCredentials.rotating_gateways` | object | Datacenter only. One rotating gateway IP per country: { "US": "<ip>", "CA": "<ip>", ... }. Picked for this order, so read it from the API instead of saving it. Only the countries this order has IPs in. |
| `proxyCredentials.rotating_gateway_sets` | object | Datacenter only. Every usable rotating gateway per country: { "US": ["<ip>", "<ip>"], ... }. The US has two, each reaching about half of the order's US IPs: point your own hostname's A-records at all of them. |
| `proxyCredentials.rotating_bound_ip_conflicts` | array | Datacenter only. Country codes where the order owns the only rotating gateway. Rotating there keeps giving the same IP while an IP is whitelisted. Usually empty. |
| `proxyCredentials.hostip` | string | IPv6: the default (worldwide) gateway IP. Other products: their host. |
| `proxyCredentials.port` | string | IPv6: "1338" (HTTP). SOCKS5 is in ports.socks5. |
| `proxyCredentials.endpoints` | object | IPv6: one gateway IP per exit country: { ww, us, de, gb, nl }. The same login works on all of them. Premium Residential (resix): { http: { americas, europe, asia }, socks5: { americas, europe, asia } }, each an "ip:port". |
| `proxyCredentials.isp_pools` | array | Premium Residential (resix) only. The six dedicated ISP pools this order can also use with the same login: [{ id, name, country, protocol, endpoint }], e.g. { id: "us", name: "United States", country: "US", protocol: "http", endpoint: "<ip>:6000" }. One country per pool, HTTP only, no targeting flags (bare password or -session-<8>_lifetime-<h> only). Read the endpoint from here each time rather than saving it. |
| `proxyCredentials.ports` | object | IPv6: { http: "1338", socks5: "1339" }. |
| `proxyCredentials.usage` | object|null | IPv6: { available_bandwidth, used_bandwidth, bandwidth_left } in GB on bandwidth plans; null on unlimited (speed-capped) plans. |
| `proxyCredentials.auth_mode` | string | ISP: always "logpas" (username and password). |
| `package_region` | string|null | Custom ISP: what was bought, as a JSON string { kind: "customisp", tier, location, asn, udp, length, replacements, carrierName }. asn and carrierName are null when no carrier was picked. |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/order/700c853f-9818-4162-bed9-10003d3cb85e" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
// Standard Residential order
{
  "type": "response",
  "message": "success",
  "payload": {
"order": {
  "id": "700c853f-9818-4162-bed9-10003d3cb85e",
  "package_title": "Residential 1GB",
  "status": "CONFIRMED",
  "proxyCredentials": {
    "username": "mtpeqbcevnemewc201120",
    "password": "jonxybfeuu",
    "hostip": "203.0.113.10",
    "port": "9000-12000",
    "total_bandwidth": 2,
    "used_bandwidth": 0,
    "bandwidth_left": 2,
    "available_bandwidth": 2,
    "expired_at": "2026-02-20T11:48:45.000Z"
  }
}
  },
  "pagination": {}
}

// Datacenter order (all IPs are documentation examples; read yours from the API)
{
  "type": "response",
  "message": "success",
  "payload": {
"order": {
  "id": "c5c7c6b5-e488-420c-b436-e90e86031751",
  "package_title": "Datacenter Proxies - 1 Month - 100 IPs - 250GB",
  "package_proxyType": "DatacenterP",
  "package_ips": 100,
  "package_bandwidth": 250,
  "package_days": 30,
  "status": "CONFIRMED",
  "extendOrders": [],
  "proxyCredentials": {
    "username": "dcuser482913",
    "password": "dcpass91827",
    "total_bandwidth": 250,
    "used_bandwidth": 12.4,
    "bandwidth_left": 237.6,
    "available_bandwidth": 237.6,
    "days": 30,
    "expired_at": "2026-10-11T10:00:00.000Z",
    "port_http": "1338",
    "port_socks5": "1339",
    "whitelist_ip": [],
    "ip_list": [
      {
        "iso2": "US",
        "name": "United States",
        "count_ips": 60,
        "cities": [{ "name": "New York", "ips": ["203.0.113.24", "203.0.113.57", "..."] }]
      },
      {
        "iso2": "DE",
        "name": "Germany",
        "count_ips": 40,
        "cities": [{ "name": "Frankfurt", "ips": ["203.0.113.140", "..."] }]
      }
    ],
    "country_list": { "countries": { "US": 60, "DE": 40 }, "total": 100 },
    "rotating_gateways": {
      "US": "198.51.100.10",
      "DE": "198.51.100.40"
    },
    "rotating_gateway_sets": {
      "US": ["198.51.100.10", "198.51.100.20"],
      "DE": ["198.51.100.40"]
    },
    "rotating_bound_ip_conflicts": [],
    "note": "Datacenter. Every IP in ip_list is a dedicated proxy: ... (connection help as plain text)"
  }
}
  },
  "pagination": {}
}

// Premium Residential order (resix). isp_pools are the dedicated ISP pools, HTTP only
{
  "type": "response",
  "message": "success",
  "payload": {
"order": {
  "id": "3c4d5e6f-7a8b-4c9d-8e0f-a1b2c3d4e5f6",
  "package_title": "Premium Residential 5GB",
  "package_proxyType": "resix",
  "status": "CONFIRMED",
  "proxyCredentials": {
    "username": "hftjvbtwgxxxceb205604",
    "password": "ngnstzylne",
    "total_bandwidth": 5,
    "used_bandwidth": 1.2,
    "bandwidth_left": 3.8,
    "available_bandwidth": 3.8,
    "expired_at": "2026-12-01T10:00:00.000Z",
    "hostip": "203.0.113.20",
    "port": "6011",
    "ip": null,
    "usage": { "available_bandwidth": 3.8, "used_bandwidth": 1.2, "bandwidth_left": 3.8 },
    "endpoints": {
      "http": { "americas": "203.0.113.20:6011", "europe": "203.0.113.21:6012", "asia": "203.0.113.22:6013" },
      "socks5": { "americas": "203.0.113.20:6014", "europe": "203.0.113.21:6015", "asia": "203.0.113.22:6016" }
    },
    "isp_pools": [
      { "id": "us", "name": "United States", "country": "US", "protocol": "http", "endpoint": "203.0.113.30:6000" },
      { "id": "us_tickets", "name": "United States (Tickets)", "country": "US", "protocol": "http", "endpoint": "203.0.113.30:6001" },
      { "id": "uk", "name": "United Kingdom", "country": "GB", "protocol": "http", "endpoint": "203.0.113.31:6003" },
      { "id": "de", "name": "Germany", "country": "DE", "protocol": "http", "endpoint": "203.0.113.31:6006" },
      { "id": "de_dtag", "name": "Germany (DTAG)", "country": "DE", "protocol": "http", "endpoint": "203.0.113.31:6007" },
      { "id": "au", "name": "Australia", "country": "AU", "protocol": "http", "endpoint": "203.0.113.32:6002" }
    ],
    "note": "Premium Residential X. Connect through the endpoint that matches your target country's region ... (connection help as plain text)"
  }
}
  },
  "pagination": {}
}

// IPv6 bandwidth order (for an unlimited plan, usage and total_bandwidth are null)
{
  "type": "response",
  "message": "success",
  "payload": {
"order": {
  "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
  "package_title": "IPv6 50GB",
  "package_proxyType": "Ipv6p",
  "status": "CONFIRMED",
  "proxyCredentials": {
    "username": "ipv6user291",
    "password": "v6pass817",
    "total_bandwidth": 50,
    "used_bandwidth": 12.6,
    "bandwidth_left": 37.4,
    "available_bandwidth": 37.4,
    "expired_at": "2026-10-02T08:30:00.000Z",
    "hostip": "203.0.113.60",
    "port": "1338",
    "ip": null,
    "usage": { "available_bandwidth": 37.4, "used_bandwidth": 12.6, "bandwidth_left": 37.4 },
    "endpoints": {
      "ww": "203.0.113.60",
      "us": "203.0.113.60",
      "de": "203.0.113.61",
      "gb": "203.0.113.62",
      "nl": "203.0.113.63"
    },
    "ports": { "http": "1338", "socks5": "1339" },
    "note": "IPv6. Pick the exit country by gateway host (see endpoints); ... (connection help as plain text)"
  }
}
  },
  "pagination": {}
}

// Custom ISP order. The per-IP lines are on GET /order/:id/proxies
{
  "type": "response",
  "message": "success",
  "payload": {
"order": {
  "id": "9d8e7f6a-5b4c-4d3e-8f2a-1b0c9d8e7f6a",
  "package_title": "Custom ISP - Standard - 1 Month",
  "package_proxyType": "Isp",
  "package_ips": 5,
  "package_region": "{\"kind\":\"customisp\",\"tier\":\"standard\",\"location\":\"United States - New York City\",\"asn\":7922,\"udp\":false,\"length\":\"1_month\",\"replacements\":2,\"carrierName\":\"Comcast Cable Communications\"}",
  "status": "CONFIRMED",
  "proxyCredentials": {
    "username": "cpx8f31a2",
    "password": "Rk4pX9wq",
    "expired_at": "2026-11-12T12:00:00.000Z",
    "hostip": null,
    "port": "1337",
    "ip": null,
    "auth_mode": "logpas",
    "whitelist_ip": [],
    "note": "Custom ISP. Authenticated by username:password; HTTP and SOCKS5 on the same port. Get one line per IP from GET /order/:id/proxies ..."
  }
}
  },
  "pagination": {}
}
```

#### `GET /order/:id/extendOptions`

Get available extension packages for a specific order, with the exact price POST /order/:id/extend will take from your balance. Read-only, charges nothing. It does not check whether the plan has expired: POST /order/:id/extend refuses an expired plan with 403.

• Standard Residential / Premium Residential / Rotating Mobile: returns your assigned bandwidth packs. These options have no extendType field: they always add bandwidth.
• Custom ISP (Isp): returns a single option, the order's own package (same length) at resellerPrice × the order's IP count, plus carrierFeePerIp × the IP count when the order was bought on a carrier. IP replacements are not renewed. extendType is "days". packageId is resolved from the order, so you can omit it on /extend.
• Dedicated ISP (IspP): returns a single option, the order's original package at resellerPrice × the order's IP count. extendType is "days" (a fixed 30-day renewal). packageId is auto-resolved from the order, so you can omit it on /extend.
• Datacenter (DatacenterP): returns a single option, the order's own package at your resellerPrice × the add-on multiplier (+25% for each add-on the plan was bought with: high_concurrency, high_priority, extra whitelist slots). extendType is "days". packageId is resolved from the order, so you can omit it on /extend. note carries the same warning as the renew window on the website: "Renewing resets your data to 250 GB for the new term. Any data you haven't used yet doesn't carry over. Your remaining days do: the new term is added on top of your current end date." (250 is the plan's own GB; note is null on a plan with unlimited bandwidth.)
• IPv6 (Ipv6p): returns a single option, the order's own package at your resellerPrice. Bandwidth plans get its GB added (extendType "bandwidth"), unlimited plans get its days (extendType "days"). packageId is resolved from the order, so you can omit it on /extend. note says which of the two applies.
• Anything not listed above: returns an empty list (extending not supported).

For Datacenter and IPv6, price is exactly what the renewal charges. No VAT is added on reseller balance renewals. An empty list on a Datacenter or IPv6 order means the order's package is missing or no longer assigned to your account; contact us. On an IPv6 bandwidth plan it can also mean the renewal would take the plan past 5000 GB in total, the most an IPv6 plan can hold.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | The order ID to get extension options for |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `packages[].id` | string | Package ID. Pass as packageId in POST /extend (optional for ISP, Datacenter and IPv6). |
| `packages[].price` | number | What POST /extend charges for this option. ISP: resellerPrice × order quantity (Custom ISP: plus carrierFeePerIp × quantity when the order has a carrier). Datacenter: resellerPrice × add-on multiplier. IPv6: resellerPrice. No VAT is added. |
| `packages[].resellerPrice` | number | Your price for the package itself. For ISP this is the per-IP price. |
| `packages[].originalPrice` | number | The retail price for the same renewal (multiplied the same way as price), for comparison. |
| `packages[].discount` | number | Your discount versus originalPrice, in percent. |
| `packages[].savings` | number | originalPrice minus price, in USD. |
| `packages[].bandwidth` | string|number|null | GB in the package. Residential / Mobile: a string (e.g. "1"). Datacenter and IPv6: a number (0 or null on an unlimited plan). ISP: null. |
| `packages[].days` | number | Days in the package. Added to the end date on ISP, Datacenter and unlimited IPv6 renewals. |
| `packages[].ips` | number|null | ISP: quantity (IPs) from the order. Datacenter: the plan's IP count. Otherwise null. |
| `packages[].speed` | number|null | IPv6: speed cap in Mbps (0 or null = no cap). Otherwise null. |
| `packages[].extendType` | string | Only on ISP, Datacenter and IPv6 options. "bandwidth" when the renewal adds GB (IPv6 bandwidth plans), "days" when it adds time (ISP, Datacenter, IPv6 unlimited plans). Residential and Mobile packs have no extendType: they always add bandwidth. |
| `packages[].note` | string|null | Datacenter and IPv6 only. What the renewal does to the plan's data and days, in plain words. Worth passing on to your customer. On Datacenter: the data-reset warning, or null on unlimited bandwidth. On IPv6: what gets added (days on unlimited plans; GB plus the plan's own term on bandwidth plans). |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/order/700c853f-9818-4162-bed9-10003d3cb85e/extendOptions" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
// Residential / Mobile order
{
  "payload": {
"packages": [
  {
    "id": "cdd0c81b-43fd-4bef-9709-2173a5ad3c5f",
    "title": "Residential 1GB",
    "price": 2.50,
    "resellerPrice": 2.50,
    "bandwidth": "1",
    "days": 90,
    "proxyType": "v2resi"
  }
],
"totalPackages": 5
  }
}

// Custom ISP order (5 IPs, bought on a carrier) - always 1 option
// price = 3.20 x 5 + 0.35 x 5 = 17.75
{
  "payload": {
"packages": [
  {
    "id": "a1b2c3d4-0003-4a5b-8c9d-0e1f2a3b4c5d",
    "title": "Custom ISP - Standard - 1 Month",
    "price": 17.75,
    "originalPrice": 21.75,
    "resellerPrice": 3.2,
    "discount": 18.39,
    "savings": 4,
    "bandwidth": null,
    "days": 30,
    "ips": 5,
    "proxyType": "Isp",
    "extendType": "days"
  }
],
"totalPackages": 1
  }
}

// Dedicated ISP order - always 1 option, price = resellerPrice × package_ips
{
  "payload": {
"packages": [
  {
    "id": "a3f2c1d4-9e8b-4f7a-bc3d-2e1f0a9b8c7d",
    "title": "Dedicated ISP - 30 days",
    "price": 12.50,
    "resellerPrice": 2.50,
    "bandwidth": null,
    "days": 30,
    "ips": 5,
    "proxyType": "IspP",
    "extendType": "days"
  }
],
"totalPackages": 1
  }
}

// Datacenter order - always 1 option: the order's own package.
// price = resellerPrice x add-on multiplier (here 1 add-on, so x1.25)
{
  "type": "response",
  "message": "success",
  "payload": {
"packages": [
  {
    "id": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
    "title": "Datacenter Proxies - 1 Month - 100 IPs - 250GB",
    "description": "100 dedicated datacenter IPs, 250 GB, 30 days",
    "price": 100,
    "originalPrice": 125,
    "resellerPrice": 80,
    "discount": 20,
    "savings": 25,
    "bandwidth": 250,
    "days": 30,
    "proxyType": "DatacenterP",
    "ips": 100,
    "region": null,
    "speed": null,
    "extendType": "days",
    "note": "Renewing resets your data to 250 GB for the new term. Any data you haven't used yet doesn't carry over. Your remaining days do: the new term is added on top of your current end date."
  }
],
"totalPackages": 1
  },
  "pagination": {}
}

// IPv6 bandwidth order - always 1 option: the order's own package
{
  "type": "response",
  "message": "success",
  "payload": {
"packages": [
  {
    "id": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
    "title": "IPv6 50GB",
    "description": "IPv6 rotating proxies, 50 GB",
    "price": 12,
    "originalPrice": 15,
    "resellerPrice": 12,
    "discount": 20,
    "savings": 3,
    "bandwidth": 50,
    "days": 30,
    "proxyType": "Ipv6p",
    "ips": null,
    "region": null,
    "speed": 0,
    "extendType": "bandwidth",
    "note": "Renewing adds the package's GB on top of the data left, and moves the end date forward by 30 days (the plan's own term)."
  }
],
"totalPackages": 1
  },
  "pagination": {}
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 404 | Order not found | No reseller order with that id on your account. |
| 500 | Internal server error | Our side failed. Nothing was charged; safe to retry. |

#### `POST /order/:id/extend`

Renew an existing order, paid from your reseller balance. What a renewal adds depends on the product:

• Standard Residential, Premium Residential, Rotating Mobile: the GB of the package you pick from /extendOptions are added. packageId is required.
• Custom ISP: the same package again (same length, same IPs) at resellerPrice × the order's IP count, plus carrierFeePerIp × the IP count when the order was bought on a carrier. IP replacements are not renewed.
• Dedicated ISP: another full period at resellerPrice × the order's IP count.
• Datacenter: another term of the order's OWN package, at your resellerPrice × the add-on multiplier (+25% per add-on bought with the plan). Renewing resets the data to the plan's full GB for the new term. Any data not used yet doesn't carry over. The remaining days do: the new term is added on top of the current end date.
• IPv6: the order's OWN package at your resellerPrice. An unlimited (speed) plan gets the package's days added on top of the current end date. A bandwidth plan gets the package's GB added on top of the data left, and its end date moves forward by the plan's own term.

For ISP, Datacenter and IPv6 orders, packageId is optional and ignored: the API always uses the order's own package. Send an empty JSON body {}. A body is required (it must be valid JSON), and packageId is the only field it accepts.

No VAT is added on reseller balance renewals. The price is exactly packages[].price from GET /order/:id/extendOptions, so check it there first.

The response already contains the updated order (same shape as GET /order/:id), so order.proxyCredentials.expired_at is the new end date. You can also read GET /order/:id again.

Renewals are not idempotent: a second call after the first one succeeded renews, and charges, again. Datacenter, IPv6 and Custom ISP run one renewal per order at a time: a call that arrives while one is still running gets 409 and is not charged. An order whose last renewal is under review cannot be renewed until our team has finished it. If a call times out, read GET /order/:id and compare expired_at before sending it again.

READ THE MESSAGE ON A 500. "Failed to extend proxy plan. Your balance has been refunded." means nothing happened and your balance is back: safe to retry later. "Your renewal payment went through, but it needs manual review by our team..." means you WERE charged and our team applies the renewal within 24 hours or refunds it: do NOT retry. On Datacenter, IPv6 and Custom ISP a retry only gets 409 "This plan's last renewal is being checked by our team...", so it cannot charge you twice, but it will not help either.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | The order ID to extend |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `packageId` | string | no | Extension package ID from /extendOptions. Required for Residential/Mobile. Optional and ignored for ISP, Datacenter and IPv6 (the order's own package is used). The body must still be JSON: send {}. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `extendOrderId` | string | ID of the created extension order. Keep it as your reference. |
| `order` | object | The renewed order, same shape as GET /order/:id. |
| `order.proxyCredentials.expired_at` | string | The new end date (ISP, Datacenter, IPv6). |
| `order.proxyCredentials.bandwidth_left` | number | Remaining bandwidth after the renewal (Residential, Mobile, IPv6 bandwidth plans; on Datacenter the full plan GB again). |
| `order.proxyCredentials.available_bandwidth` | number | Legacy alias of bandwidth_left. |

Request:

```bash
# Datacenter, IPv6, Custom ISP, Dedicated ISP: the order's own package, empty body
curl -X POST "https://catproxies.com/api/v1/public/order/c5c7c6b5-e488-420c-b436-e90e86031751/extend" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'

# Residential / Mobile - pass packageId from /extendOptions
curl -X POST "https://catproxies.com/api/v1/public/order/8a08caa7-e1f2-487e-aedb-9ebc018ab8f0/extend" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "packageId": "cdd0c81b-43fd-4bef-9709-2173a5ad3c5f" }'
```

Response:

```json
// Datacenter order, renewed for 30 days. order is the same shape as GET /order/:id
{
  "type": "response",
  "message": "Plan extended successfully! Your proxy expiration has been updated.",
  "payload": {
"extendOrderId": "3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904",
"order": {
  "id": "c5c7c6b5-e488-420c-b436-e90e86031751",
  "package_proxyType": "DatacenterP",
  "status": "CONFIRMED",
  "extendOrders": [
    { "id": "3b0a7c41-2f18-4d0a-9c33-5a1e77b2c904", "type": "days", "value": "30", "amount": 100, "status": "CONFIRMED" }
  ],
  "proxyCredentials": {
    "username": "dcuser482913",
    "password": "dcpass91827",
    "total_bandwidth": 250,
    "used_bandwidth": 0,
    "bandwidth_left": 250,
    "available_bandwidth": 250,
    "expired_at": "2026-11-10T10:00:00.000Z",
    "port_http": "1338",
    "port_socks5": "1339",
    "...": "ip_list, rotating_gateways and the rest, as on GET /order/:id"
  }
}
  },
  "pagination": {}
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 400 | Validation error | The body has a field other than packageId. |
| 400 | Insufficient balance. Required: $100.00, Available: $42.10 | Top up your reseller balance. Nothing was charged. Match on the start of the message: in rare cases it is just "Insufficient balance" without the amounts. |
| 400 | This plan can't be renewed automatically. Please contact us and we'll renew it for you. | Datacenter or IPv6 order with no package on record. Nothing was charged; contact us. |
| 400 | An IPv6 plan can hold at most 5000 GB. This package would take yours to 5250 GB, so please pick a smaller one. | IPv6 bandwidth plan: the renewal would take the plan past 5000 GB in total. Nothing was charged. |
| 403 | Cannot extend an expired proxy | The plan has already run out. Buy a new one. |
| 403 | Please add a name and address to complete your profile. This is required in order for invoices to be created. | Complete the profile on your account once. Also 403: "Plan extensions from your country are not allowed..." and "Unauthorized access to this order". |
| 404 | Order not found | No order with that id. |
| 404 | Reseller pricing not found for this package | The order's package is no longer assigned to your account. Nothing was charged; contact us. |
| 409 | A renewal for this plan is already in progress. Please wait a moment for it to finish before trying again. | Datacenter, IPv6 and Custom ISP. A renewal of this order is still running. This call was not charged. Wait, then check GET /order/:id before renewing again. |
| 409 | This plan's last renewal is being checked by our team. Please don't renew it again yet, contact us and we'll sort it out. | Datacenter, IPv6 and Custom ISP. An earlier renewal is under manual review. This call was not charged. Contact us. |
| 409 | This plan is already being reviewed by our team. Please don't renew it yet, contact us and we'll sort it out. | Dedicated ISP. The plan is under manual review. This call was not charged. Contact us. |
| 500 | Failed to extend proxy plan. Your balance has been refunded. | Nothing happened and the money is back on your balance. Safe to retry later. |
| 500 | Your renewal payment went through, but it needs manual review by our team. It will be applied within 24 hours or refunded. Please don't try again. | You WERE charged and our team finishes it. Do NOT retry. |

#### `PATCH /order/:id/whitelist`

Add an IP to a Datacenter order's whitelist. Whitelisted IPs use the proxy without username:password. 1 slot by default, 3 with the whitelisted_ips addon; accepts IPv4 or IPv6.

Only Datacenter has an IP whitelist. Every other product is username:password only.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | A Datacenter order ID |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `ip` | string | yes | IP to whitelist, IPv4 or IPv6. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `whitelist_ip` | array | Current list of whitelisted IPs after the update |
| `slots_used` | number | Whitelist slots currently used |
| `slots_max` | number | Max slots (1 default, 3 with the whitelisted_ips addon) |

Request:

```bash
curl -X PATCH "https://catproxies.com/api/v1/public/order/c5c7c6b5-e488-420c-b436-e90e86031751/whitelist" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
"ip": "203.0.113.42"
  }'
```

Response:

```json
{
  "type": "response",
  "message": "IP whitelisted successfully",
  "payload": {
"whitelist_ip": ["203.0.113.42"],
"slots_used": 1,
"slots_max": 3
  }
}
```

#### `DELETE /order/:id/whitelist`

Remove an IP from a Datacenter order's whitelist.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | A Datacenter order ID |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `ip` | string | yes | IP to remove, IPv4 or IPv6. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `whitelist_ip` | array | Current list of whitelisted IPs after removal |
| `slots_used` | number | Whitelist slots currently used |
| `slots_max` | number | Max whitelist slots available |

Request:

```bash
curl -X DELETE "https://catproxies.com/api/v1/public/order/c5c7c6b5-e488-420c-b436-e90e86031751/whitelist" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
"ip": "203.0.113.42"
  }'
```

Response:

```json
{
  "type": "response",
  "message": "IP removed from whitelist",
  "payload": {
"whitelist_ip": [],
"slots_used": 0,
"slots_max": 3
  }
}
```

#### `GET /order/:id/whitelist`

List the IPs currently whitelisted for a Datacenter order, with slots_used / slots_max.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | A Datacenter order ID |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `whitelist_ip` | array | IPs currently whitelisted for this order |
| `slots_used` | number | Whitelist slots in use |
| `slots_max` | number | Max slots (1, or 3 with the whitelisted_ips addon) |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/order/c5c7c6b5-e488-420c-b436-e90e86031751/whitelist" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"whitelist_ip": ["203.0.113.42"],
"slots_used": 1,
"slots_max": 3
  }
}
```

#### `GET /order/:id/stats`

Read-only usage analytics for one Datacenter or IPv6 order: traffic and requests over time, a log of recent connections, and the sites used most. It never changes the plan and costs nothing, so it is safe to show to your customers.

Pick what you want with view:
• overview (default): uploaded, downloaded and total GB, requests and success rate over time, one row per interval.
• activity: recent connections, 100 per page. Page through with page.
• top-domains: the sites used most, with requests, success rate, bandwidth and average duration.

The figures come from the live network and lag a little behind real time. available: false means the stats source could not be reached just now; the rest of the payload is then empty, so retry later rather than reading it as zero traffic. metrics is always null on Datacenter and IPv6.

GET /order/:id/unlimited-metrics is the older name of this endpoint. It takes the same parameters and returns exactly the same thing, and keeps working for integrations that already call it.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | A Datacenter or IPv6 order ID |

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `view` | string | no | "overview", "activity" or "top-domains". |
| `timeframe` | string | no | How far back to look: "1hour", "6hours", "1day", "1week", "1month" or "3months". Any other value falls back to "1day". |
| `interval` | string | no | overview only. Size of each row: "5min", "30min", "1hour", "6hour", "1day", "1week" or "1month". Any other value falls back to "1hour". |
| `page` | number | no | activity only. Page number, 100 connections per page. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `available` | boolean | false = the stats source could not be reached just now and the rest is empty. Retry later; it does not mean zero traffic. |
| `metrics` | object|null | overview only. Always null on Datacenter and IPv6. |
| `series[].timestamp` | string | Start of the row, as a "YYYY-MM-DD HH:MM:SS" string (a space, not a T). |
| `series[].uploaded_gb / downloaded_gb / total_gb` | number | Traffic in that row, in GB. |
| `series[].requests` | number | Requests in that row. |
| `series[].success_rate` | number | Share of successful requests, in percent (0 to 100). |
| `activity[].timestamp` | string | When the connection happened, as a "YYYY-MM-DD HH:MM:SS" string. |
| `activity[].outcome` | string | Result of the connection, for example SUCCESS or ERROR. |
| `activity[].downloaded_bytes / uploaded_bytes` | number | Traffic of that connection, in bytes. |
| `activity[].duration_ms` | number | How long the connection took, in milliseconds. |
| `activity[].client_ip` | string | The address the connection came from (the proxy user's side). |
| `pagination` | object | activity only, inside payload. total rows, per_page (100), current_page and last_page. |
| `topDomains[].success_rate` | number | Share of successful requests to that site, in percent. |
| `topDomains[].total_bandwidth / total_downloaded / total_uploaded` | number | Traffic to that site, in bytes. |
| `topDomains[].avg_duration_ms` | number | Average request duration, in milliseconds. |
| `summary` | object|null | top-domains only. Totals for the timeframe as name: number pairs, for example total_domains, total_requests, total_bandwidth (bytes) and total_bandwidth_gb. Only numbers are passed on and the set of names can change, so read them defensively. null when there is none. |

Request:

```bash
# Traffic over the last week, one row per day
curl -X GET "https://catproxies.com/api/v1/public/order/c5c7c6b5-e488-420c-b436-e90e86031751/stats?timeframe=1week&interval=1day" \
  -H "Authorization: Bearer cp_your_api_key_here"

# Recent connections in the last hour, page 2
curl -X GET "https://catproxies.com/api/v1/public/order/c5c7c6b5-e488-420c-b436-e90e86031751/stats?view=activity&timeframe=1hour&page=2" \
  -H "Authorization: Bearer cp_your_api_key_here"

# The sites used most today
curl -X GET "https://catproxies.com/api/v1/public/order/c5c7c6b5-e488-420c-b436-e90e86031751/stats?view=top-domains" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
// view=overview (Datacenter or IPv6, so metrics is null)
{
  "type": "response",
  "message": "Success",
  "payload": {
"metrics": null,
"series": [
  {
    "timestamp": "2026-09-10 00:00:00",
    "uploaded_gb": 0.000473,
    "downloaded_gb": 0.283713,
    "total_gb": 0.284186,
    "requests": 245,
    "success_rate": 100
  },
  {
    "timestamp": "2026-09-11 00:00:00",
    "uploaded_gb": 0.000391,
    "downloaded_gb": 0.201577,
    "total_gb": 0.201968,
    "requests": 188,
    "success_rate": 98.9
  }
],
"timeframe": "1week",
"interval": "1day",
"available": true
  },
  "pagination": {}
}

// view=activity
{
  "type": "response",
  "message": "Success",
  "payload": {
"activity": [
  {
    "timestamp": "2026-09-11 08:00:56",
    "domain": "example.com",
    "outcome": "SUCCESS",
    "protocol": "SOCKS5",
    "downloaded_bytes": 228,
    "uploaded_bytes": 77,
    "duration_ms": 284,
    "client_ip": "203.0.113.77"
  }
],
"pagination": { "total": 164, "per_page": 100, "current_page": 2, "last_page": 2 },
"timeframe": "1hour",
"available": true
  },
  "pagination": {}
}

// view=top-domains
{
  "type": "response",
  "message": "Success",
  "payload": {
"topDomains": [
  {
    "domain": "example.com",
    "requests": 29,
    "success_rate": 100,
    "total_bandwidth": 242297,
    "total_downloaded": 186472,
    "total_uploaded": 55825,
    "avg_duration_ms": 145.2
  }
],
"summary": {
  "total_domains": 13,
  "total_requests": 463,
  "total_bandwidth": 284284595,
  "total_bandwidth_gb": 0.2843
},
"timeframe": "1day",
"available": true
  },
  "pagination": {}
}

// Stats source not reachable right now (retry later)
{
  "type": "response",
  "message": "Success",
  "payload": { "metrics": null, "series": [], "timeframe": "1day", "interval": "1hour", "available": false },
  "pagination": {}
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 404 | Order not found | No reseller order with that id on your account. |
| 400 | Usage statistics are available for Datacenter, IPv6 and Unlimited Residential plans only. | The order is another product. Only Datacenter and IPv6 have usage stats. |
| 400 | This plan has no proxy yet. | The plan is still being set up. Try again shortly. |
| 500 | Internal server error | Our side failed. Safe to retry. |

#### `POST /order/:id/reset-password`

Generate a new password for a Standard Residential, Premium Residential, IPv6 or Datacenter order and return it. The new password takes a few minutes to activate everywhere, and until then the old one may still work on some locations - update your integrations right away. Available for Standard Residential, Premium Residential, IPv6, Datacenter and Rotating Mobile orders. The returned password length depends on the product (Standard Residential ~36 characters, Premium Residential 8 characters, IPv6 and Datacenter 12 characters). Where the targeting flags live depends on the product: on Standard Residential, IPv6 and Datacenter they are on the USERNAME, so only the password half of your proxy strings changes. On Premium Residential they are appended to the PASSWORD, so a reset invalidates every proxy string you have built and you must re-append your flags to the new password. Rotating Mobile works differently: the plan gets a whole new login and its remaining data moves onto it, so the USERNAME changes too and the response also carries `username`. The old mobile login stops working straight away, used and total GB carry over, and a mobile plan can be reset once per hour (not within 5 minutes of a top-up). Rate limited to 3 requests per minute per reseller account. No request body.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | A Standard Residential, Premium Residential, IPv6, Datacenter or Rotating Mobile order ID |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `password` | string | The new proxy password (GET /order/:id returns it from now on) |
| `username` | string | Rotating Mobile only: the new proxy username (the old one stops working) |

Request:

```bash
curl -X POST "https://catproxies.com/api/v1/public/order/8a08caa7-e1f2-487e-aedb-9ebc018ab8f0/reset-password" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Proxy password reset successfully",
  "payload": {
"password": "the new proxy password",
"username": "only on Rotating Mobile: the new proxy username"
  }
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 429 | Too many requests. Please wait a moment. | More than 3 resets in a minute on your account. Wait a minute and try again. |
| 404 | Order not found | No reseller order with that id on your account. |
| 400 | Password reset is only available for Standard, Premium and Unlimited Residential, IPv6, Datacenter and Mobile orders | The order is another product (Custom ISP, Dedicated ISP). |
| 400 | This plan has no data left, so there is nothing to move to a new password. Top it up first, then reset. | Rotating Mobile only: a plan with 0 GB left cannot be moved to a new login. |
| 409 | This plan was just topped up. Please wait about 5 minutes, then reset the password. | Rotating Mobile only: the plan was extended in the last 5 minutes. |
| 429 | You can reset this plan's password once an hour. Please try again in 12 minutes. | Rotating Mobile only: one reset per plan per hour. |
| 502 | We couldn't reset the password right now. Nothing was changed on your plan, please try again in a few minutes. | Rotating Mobile only: the reset did not happen and the login is unchanged. Safe to retry. |
| 400 | This order has expired. | Expired plans cannot be reset. |
| 400 | This order has no proxy to reset. | The plan is still being set up. Try again shortly. |
| 502 | Failed to reset password. Please try again. | The reset did not go through. Safe to retry. |
| 500 | Internal server error | Our side failed. Check GET /order/:id before retrying. |

#### `GET /order/:id/proxies`

Get the individual proxy lines for a Custom ISP (Isp) or Dedicated ISP (IspP) order. Each order provisions several IP:port endpoints, each with its own login/password - this returns that list. ISP orders only (other products return their credentials directly on GET /order/:id); returns 400 for any other proxy type. If the order is still being provisioned right after purchase, you get HTTP 202 with an empty list - retry in a few seconds.

Custom ISP: every line uses port 1337 for both HTTP and SOCKS5, and the line format is host:port:login:password. host is a plain server IP, or your own hostname once it is set up (see /custom-isp/domain), never one of our names; read the lines each time, servers can change. The order has one password; each line's login carries its IP, and ip is the address the line exits from. Pick the connection server with ?region=: connection_regions lists them. auto (the default) is your hostname itself when you have one (closest server to whoever connects), otherwise the server IP closest to where each line's IP is. The payload also has replacements { bought, used, left, pending } and clean_score (true on a Clean Score order).

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | A Custom ISP (Isp) or Dedicated ISP (IspP) order ID |

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `region` | string | no | Custom ISP only. The connection server: "auto" or an id from connection_regions, e.g. "us-east" or "de". An unknown value falls back to "auto". With your own hostname, auto is that hostname (closest server to whoever connects); without one, auto is the server closest to where each line's IP is. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `proxies[].host` | string | The proxy IP - connect to host:port_http (HTTP) or host:port_socks (SOCKS5). On Custom ISP it is a plain server IP for the chosen region, or your own hostname once it is set up (see /custom-isp/domain). Never save it: servers can change. |
| `proxies[].port_http` | number | HTTP port for this line |
| `proxies[].port_socks` | number | SOCKS5 and UDP-over-SOCKS5 port for this line. Not the same as port_http on Dedicated ISP, so always use this value. On Custom ISP both ports are 1337. |
| `proxies[].login` | string | Username for this line |
| `proxies[].password` | string | Password for this line |
| `auth_mode` | string | Always "logpas": use login/password. |
| `whitelist_ip` | string | Always null. |
| `quantity` | number | Number of proxy lines on the order |
| `location` | string|null | Order location. Always null for Dedicated ISP (IspP) by design, it is pool-based with no country. Custom ISP returns the city as "Country - City". |
| `stale` | boolean | true = served from the last-known-good cache during a brief upstream outage; the lines are still valid |
| `proxies[].ip` | string | Custom ISP only. The IP the line exits from. Send it to POST /order/:id/replace to swap it. |
| `connection_region` | string | Custom ISP only. The connection server these lines use. |
| `connection_regions` | array | Custom ISP only. Every connection server as { id, label }. Pass an id as ?region=. |
| `replacements` | object | Custom ISP only. { bought, used, left, pending }. pending is true while a replacement is being confirmed. |
| `clean_score` | boolean | Custom ISP only. true on a Clean Score (premium tier) order. |

Request:

```bash
# Dedicated ISP
curl -X GET "https://catproxies.com/api/v1/public/order/8a08caa7-e1f2-487e-aedb-9ebc018ab8f0/proxies" \
  -H "Authorization: Bearer cp_your_api_key_here"

# Custom ISP, through the US East connection server
curl -X GET "https://catproxies.com/api/v1/public/order/9d8e7f6a-5b4c-4d3e-8f2a-1b0c9d8e7f6a/proxies?region=us-east" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
// Dedicated ISP order
{
  "type": "response",
  "message": "Success",
  "payload": {
"proxies": [
  {
    "host": "203.0.113.10",
    "port_http": 61234,
    "port_socks": 62234,
    "login": "user_a1b2c3",
    "password": "pass_d4e5f6"
  }
],
"location": "US",
"quantity": 5,
"expires_at": "2026-08-14T12:00:00.000Z",
"active": true,
"auth_mode": "logpas",
"whitelist_ip": null,
"stale": false
  }
}

// Custom ISP order (Clean Score, 2 replacements bought, 1 used)
{
  "type": "response",
  "message": "Success",
  "payload": {
"proxies": [
  {
    "host": "198.51.100.40",
    "port_http": 1337,
    "port_socks": 1337,
    "login": "cpx8f31a2-203.0.113.24",
    "password": "Rk4pX9wq",
    "ip": "203.0.113.24"
  }
],
"location": "United States - New York City",
"quantity": 5,
"expires_at": "2026-11-12T12:00:00.000Z",
"active": true,
"auth_mode": "logpas",
"whitelist_ip": null,
"stale": false,
"connection_region": "us-east",
"connection_regions": [
  { "id": "auto", "label": "Auto (closest server)" },
  { "id": "us-east", "label": "US East (Virginia)" },
  { "id": "de", "label": "Germany (Frankfurt)" }
],
"replacements": { "bought": 2, "used": 1, "left": 1, "pending": false },
"clean_score": true
  }
}
```

#### `POST /order/:id/replace`

Custom ISP only. Replaces one IP of the order with a new IP in the same city, using one of the IP replacements bought with the order (ispData.replacements on POST /order). The city and UDP support stay the same; the carrier can differ.

200 means it is done: the reply has the old IP, the new IP and how many replacements are left. Read GET /order/:id/proxies again for the new line. HTTP 202 with { checking: true } means the swap is still being confirmed: do NOT send it again. Our team checks it, and replacements.pending on GET /order/:id/proxies stays true until it is done.

One replacement at a time per order, about a minute apart. The order must be active and not expired.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | A Custom ISP order ID |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `ip` | string | yes | The IP to replace, as proxies[].ip on GET /order/:id/proxies. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `oldIp` | string | The IP that was removed from the order. |
| `newIp` | string | The IP that took its place. |
| `left` | number | IP replacements left on the order. |
| `checking` | boolean | Only on HTTP 202: still being confirmed. Do not retry. |

Request:

```bash
curl -X POST "https://catproxies.com/api/v1/public/order/9d8e7f6a-5b4c-4d3e-8f2a-1b0c9d8e7f6a/replace" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "ip": "203.0.113.24" }'
```

Response:

```json
// HTTP 200, swapped
{
  "type": "response",
  "message": "IP replaced",
  "payload": {
"oldIp": "203.0.113.24",
"newIp": "203.0.113.88",
"left": 1
  }
}

// HTTP 202, still being confirmed. Do not retry.
{
  "type": "response",
  "message": "Your replacement is taking longer than usual. Our team is checking it and will sort it out, you don't need to do anything.",
  "payload": { "checking": true }
}
```

**Errors**

| Status | Message | What it means |
| --- | --- | --- |
| 400 | ip is required (one of the plan's IPs from GET /order/{id}/proxies). | ip is missing. |
| 400 | IP replacement is only available on Custom ISP orders. | The order is another product. |
| 400 | You have no IP replacements left on this plan. | Every replacement bought with the order has been used. |
| 400 | This IP is no longer on your plan. Refresh the page and try again. | The IP is not on the order any more. Read GET /order/:id/proxies again. |
| 400 | This plan has expired. | Expired orders cannot swap IPs. |
| 400 | We couldn't replace this IP right now, your replacement was not used. Please try again later or contact support. | Nothing changed and no replacement was used. Safe to try again later. |
| 404 | Order not found | No reseller order with that id on your account. |
| 409 | A replacement is already running on this plan. Please wait a moment. | Wait for the running one to finish. |
| 429 | Please wait a minute between two replacements. | Wait a minute, then try again. |
| 503 | We couldn't reach the IP list right now. Please try again in a minute. | Nothing changed. Try again shortly. |

#### `POST /order/:id/customer`

OPTIONAL. This endpoint is only for resellers who want to organise their orders by end customer. It does nothing to the proxy itself.

Tag or re-tag an existing order with your own customer id/label so that customer's plans group together in your panel. You can also set an optional free-text note, a group name, and tags. Call it again on the same order to change the values; pass an empty customerId to clear the tag.

You never have to use this. Resellers who do not tag orders keep buying and managing exactly as before, with no change to any request or response.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | The order ID to tag |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `customerId` | string | yes | Your own id or label for the end customer. Pass an empty string to clear the tag. |
| `note` | string | no | Optional free-text note to store against the order. |
| `group` | string | no | Optional group/segment name to file this customer under. |
| `tags` | array | no | Optional array of string labels for your own filtering. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `order.id` | string | Order ID |
| `order.customerId` | string | The customer id/label now stored on the order |
| `order.note` | string|null | The stored note, if any |
| `order.group` | string|null | The stored group, if any |
| `order.tags` | array | The stored tags |

Request:

```bash
curl -X POST "https://catproxies.com/api/v1/public/order/700c853f-9818-4162-bed9-10003d3cb85e/customer" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
"customerId": "acme-co",
"note": "Renewed on annual plan",
"group": "EU clients",
"tags": ["priority", "scraping"]
  }'
```

Response:

```json
{
  "type": "response",
  "message": "Order tagged successfully",
  "payload": {
"order": {
  "id": "700c853f-9818-4162-bed9-10003d3cb85e",
  "customerId": "acme-co",
  "note": "Renewed on annual plan",
  "group": "EU clients",
  "tags": ["priority", "scraping"]
}
  }
}
```

#### `GET /order/custom-options`

Returns one entry per pool family you are assigned (Standard Residential, Premium Residential, Mobile) with its per-GB rate and the packageId to pass to POST /order/custom.

Custom sizing is only available for these pooled products. Fixed-size products (ISP, Datacenter, IPv6) are not listed here, buy those with POST /order at their set size.

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `options[].packageId` | string | Pass this as packageId to POST /order/custom. |
| `options[].family` | string | White-label product family name. |
| `options[].ratePerGb` | number | Your price per GB (USD). Net cost of a custom plan = gb × ratePerGb. |
| `options[].baseGb` | number | The base package size the plan is built from (informational). |
| `options[].minGb` | number | Minimum GB you can request (0.1). |
| `options[].maxGb` | number | Maximum GB per single plan. |
| `options[].defaultDays` | number | The plan length (in days) a new custom plan in this family is created with, set for your account. A trial can be shorter (see trialDays on POST /order/custom), never longer. |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/order/custom-options" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "ok",
  "payload": {
"options": [
  {
    "packageId": "6f19b4e7-1b1e-4ed5-85e4-41e6bbe07101",
    "family": "Standard Residential",
    "title": "Standard Residential",
    "ratePerGb": 1.05,
    "baseGb": 1,
    "minGb": 0.1,
    "maxGb": 100000,
    "defaultDays": 30
  }
]
  }
}
```

#### `POST /order/custom`

Create a pooled plan (Standard Residential, Premium Residential or Mobile) at an arbitrary GB size, instead of only the fixed sizes in your store. Pass the packageId from GET /order/custom-options and the GB you want.

The GB is rounded UP to the nearest 0.1. You are charged your per-GB rate for exactly the size created, so the cost = gb × ratePerGb (see the estimate in /order/custom-options). Reseller orders carry NO VAT (resellers are invoiced once, on balance deposits). Note ratePerGb is shown rounded to cents; the actual charge uses the exact unrounded rate, so pre-computed totals can differ by a cent or two. Returns the full order including proxy credentials (same shape as GET /order/:id); on the rare partial case the returned gb is null and order may be null, so read the order back from GET /order/:id.

Optionally pass customerId (and note/group/tags) to tag the new plan to one of your customers in the same call.

PLAN LENGTH: a custom plan is created for your account’s standard plan length (a fixed number of days set for your account; see defaultDays in GET /order/custom-options). To create a shorter TRIAL plan, pass trialDays with a whole number of days below that standard length. trialDays is capped at your standard length, so you can never exceed it, and the length actually used is returned as expirationDays.

RATE LIMIT: at most 1 custom-create per 10 seconds per account (a 429 with a "plan is already being created" message means back off, a create is not idempotent).

RARE CASE: if the plan is created but the provider could not size it all the way (a transient upstream issue), the response still succeeds with "partial": true and the plan set to the size we could reach, and you were only charged for that size. Add the remaining GB later with POST /order/:id/bandwidth. Do NOT re-submit: a re-submit creates a NEW plan and a NEW charge. If this endpoint returns 409, the charge STANDS and a human is finishing the order, do NOT retry (a retry buys a second plan); everything else (400/403/502) created nothing.

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `packageId` | string | yes | A packageId from GET /order/custom-options (the family to build in). |
| `gb` | number | yes | Desired plan size in GB. Rounded UP to the nearest 0.1. |
| `customerId` | string | no | Optional. Tag the new plan to this customer id/label. |
| `note` | string | no | Optional note stored against the order. |
| `group` | string | no | Optional group/segment name. |
| `tags` | array | no | Optional array of string labels. |
| `trialDays` | number | no | Optional. Create a shorter TRIAL plan of this many whole days instead of your normal plan length. It is capped at your normal length (a higher number is clamped down). Omit for the normal length. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `orderId` | string | The new order id. Fetch full details any time with GET /order/:id. |
| `planId` | string|null | The provisioned plan (sub-user) id. |
| `family` | string | White-label product family. |
| `requestedGb` | number | The GB you asked for (after rounding up). |
| `gb` | number | The GB actually on the plan. Equals requestedGb unless partial is true. |
| `ratePerGb` | number | Your per-GB rate used for the charge. |
| `partial` | boolean | true if the plan could not be sized all the way (see description). |
| `trial` | boolean | true when you passed trialDays (a shorter trial plan was created). |
| `expirationDays` | number | The plan length in days actually used: your normal length, or the capped trialDays. |
| `newBalance` | number | Your reseller balance after the charge. |
| `order` | object | The full order incl. proxyCredentials (same as GET /order/:id). |

Request:

```bash
curl -X POST "https://catproxies.com/api/v1/public/order/custom" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
"packageId": "6f19b4e7-1b1e-4ed5-85e4-41e6bbe07101",
"gb": 25,
"customerId": "acme-co"
  }'
```

Response:

```json
{
  "type": "response",
  "message": "Custom plan created.",
  "payload": {
"orderId": "9c2b1f7a-1c2d-4e5f-9a8b-7c6d5e4f3a2b",
"planId": "sub_ab12cd34",
"family": "Standard Residential",
"requestedGb": 25,
"gb": 25,
"ratePerGb": 1.05,
"partial": false,
"trial": false,
"expirationDays": 30,
"customerId": "acme-co",
"newBalance": 148.75,
"order": { "id": "9c2b1f7a-...", "proxyCredentials": { "username": "...", "password": "..." } }
  }
}
```

#### `POST /order/:id/bandwidth`

Adjust a pooled plan (Standard Residential, Premium Residential, Mobile) after purchase. Three modes:

• "add": add GB. Charged at your per-GB rate.
• "remove": remove GB (up to the remaining amount). Refunded at your per-GB rate.
• "full": delete the whole plan; the unused GB is refunded to your balance.

Only pooled products support this. ISP, Datacenter and IPv6 plans are rejected. Money is always recomputed server-side from what you paid.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | The order to adjust. |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `mode` | string | yes | "add", "remove" or "full". |
| `gb` | number | no | How many GB to add/remove. Ignored for mode "full". |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `addedGb / removedGb` | number | GB added or removed. |
| `chargeAmount / refundAmount` | number | Amount charged (add) or refunded (remove/full). |
| `availableGb` | number | Remaining GB on the plan after the change. |
| `newBalance` | number | Your reseller balance after the change. |

Request:

```bash
curl -X POST "https://catproxies.com/api/v1/public/order/700c853f-9818-4162-bed9-10003d3cb85e/bandwidth" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "mode": "add", "gb": 5 }'
```

Response:

```json
{
  "type": "response",
  "message": "Bandwidth added.",
  "payload": {
"addedGb": 5,
"chargeAmount": 5.25,
"newBalance": 143.50,
"availableGb": 30,
"status": "CONFIRMED"
  }
}
```

#### `GET /order/:id/exchange-options`

For a pooled plan, returns the OTHER two pool families you can swap its remaining value into, with the estimated GB you would get in each. An exchange is value-preserving: it costs nothing net (you already paid for the source GB).

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | The pooled order to exchange. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `source` | object | The current plan: remaining GB, its USD value and family. |
| `options[].packageId` | string | Pass this as targetPackageId to POST /order/:id/exchange. |
| `options[].estimatedGb` | number | GB you would get in that family for the same value. |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/order/700c853f-9818-4162-bed9-10003d3cb85e/exchange-options" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "ok",
  "payload": {
"source": { "availableGb": 20, "valueUsd": 21, "ratePerGb": 1.05, "family": "Standard Residential" },
"options": [
  { "packageId": "cdd0c81b-43fd-4bef-9709-2173a5ad3c5f", "family": "Mobile", "ratePerGb": 3.5, "estimatedGb": 6 }
]
  }
}
```

#### `POST /order/:id/exchange`

Swap this pooled plan's remaining value into a different pool family (from GET /order/:id/exchange-options) for the same customer. Value-preserving, the swap nets to zero. The old plan is released and a new one is created and returned; fetch its credentials with GET /order/:id using the returned targetOrderId.

The exchange is based on the bandwidth ACTUALLY REMAINING right now (read live), never the original or extended size. RATE LIMIT: 1 exchange per 30 seconds per plan (429 if one is already in progress). RARE CASE: if the old plan was already released and refunded but the new plan could not be created, you get a 502 whose message says the value was refunded to your balance, do NOT retry (the source is already gone); buy the new plan from your store instead.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | The source pooled order. |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `targetPackageId` | string | yes | A packageId from GET /order/:id/exchange-options. |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `targetOrderId` | string | The new order id (fetch creds with GET /order/:id). |
| `targetPlanId` | string|null | The new provisioned plan id. |
| `newGb` | number | GB on the new plan. |

Request:

```bash
curl -X POST "https://catproxies.com/api/v1/public/order/700c853f-9818-4162-bed9-10003d3cb85e/exchange" \
  -H "Authorization: Bearer cp_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "targetPackageId": "6f19b4e7-1b1e-4ed5-85e4-41e6bbe07101" }'
```

Response:

```json
{
  "type": "response",
  "message": "Exchange complete.",
  "payload": {
"sourceOrderId": "700c853f-9818-4162-bed9-10003d3cb85e",
"targetOrderId": "9c2b1f7a-64d0-4a1e-9f3b-2c8a5d0e7b41",
"targetPlanId": "sub_ab12cd34",
"targetFamily": "Mobile",
"exchangedGb": 20,
"newGb": 6,
"valueUsd": 21,
"newBalance": 148.75
  }
}
```

### Targeting

#### `GET /gresi-targeting-options`

Get available targeting options for Standard Residential proxies. This endpoint is only available for Standard Residential proxy orders.

Response shape: a single "countries" array. ASNs, states and cities are all nested inside their country - there is no top-level "asns" list, and cities live under states (countries[].states[].cities[]), not directly under the country. Use the "code" of each entry in the flags below; codes are lowercase.

This payload is large (~2 MB, 214 countries, 1400+ ASNs for the US alone) and rarely changes - fetch it once and cache it rather than calling it per request.

Connecting: use the host from GET /order/:id (HTTP ports 9000 rotating / 10000 sticky, SOCKS5 11000 / 12000) and append targeting to the USERNAME: -type-residential (required), -country-us, -state-<s>, -city-<c>, -asn-<n>. For a sticky IP add -lifetime-<minutes>-session-<id> (hold up to 1440 minutes = 24h); omit the sticky flags for a fresh IP each request.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | Order ID of a Standard Residential proxy order |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `countries[].code` | string | Country code, lowercase ISO-2 (use in -country-) |
| `countries[].name` | string | Country display name |
| `countries[].asns[].code` | string | ASN number only, no "AS" prefix (use in -asn-) |
| `countries[].asns[].name` | string | ASN display name, prefixed with its number |
| `countries[].states[].code` | string | State code, lowercase full name, no spaces (use in -state-) |
| `countries[].states[].name` | string | State display name |
| `countries[].states[].cities[].code` | string | City code, lowercase, no spaces or separators (use in -city-) |
| `countries[].states[].cities[].name` | string | City display name |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/gresi-targeting-options?orderId=8a08caa7-e1f2-487e-aedb-9ebc018ab8f0" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"countries": [
  {
    "code": "us",
    "name": "United States",
    "asns": [
      { "code": "7922", "name": "AS7922 Comcast Cable Communications, LLC" }
    ],
    "states": [
      {
        "code": "california",
        "name": "California",
        "cities": [
          { "code": "eastlosangeles", "name": "East Los Angeles" }
        ]
      }
    ]
  }
]
  }
}
```

#### `GET /mobile-targeting-options/countries`

Returns all countries available for Rotating Mobile targeting.

Connecting: use the host/port from GET /order/:id and append targeting to the USERNAME: -country-us, -region-<r>, -city-<c>, -isp-<i>. For a sticky IP add -sid-<id>-ttl-<duration>, where duration is 1m, 5m, 10m, 30m, 60m, 120m, 180m or 240m (hold up to 4h); omit the sticky flags for a fresh IP each request.

Cache this response - the list rarely changes (recommended TTL: 1 hour). Rate limited to 60 requests per 10 minutes per API key, shared across all 4 mobile targeting endpoints. For bulk setup, use the offline snapshot instead.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | Order ID of a Rotating Mobile proxy order |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `countries[].code` | string | Country code (lowercase) |
| `countries[].name` | string | Country display name |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/mobile-targeting-options/countries?orderId=YOUR_ORDER_ID" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"countries": [
  { "code": "us", "name": "United States" },
  { "code": "gb", "name": "United Kingdom" },
  { "code": "de", "name": "Germany" }
]
  }
}
```

#### `GET /mobile-targeting-options/regions`

Returns available regions (states/provinces) for a given country. Cache by country code (recommended TTL: 1 hour).

Rate limited to 60 requests per 10 minutes per API key, shared across all 4 mobile targeting endpoints.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | Order ID of a Rotating Mobile proxy order |
| `country` | string | yes | Country code (e.g. "us") |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `regions[].code` | string | Region code (lowercase full name, spaces as "+", e.g. "new+york") |
| `regions[].name` | string | Region display name |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/mobile-targeting-options/regions?orderId=YOUR_ORDER_ID&country=us" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"regions": [
  { "code": "california", "name": "California" },
  { "code": "new+york", "name": "New York" },
  { "code": "texas", "name": "Texas" }
]
  }
}
```

#### `GET /mobile-targeting-options/cities`

Returns available cities within a country + region. Cache by country+region key (recommended TTL: 1 hour).

Rate limited to 60 requests per 10 minutes per API key, shared across all 4 mobile targeting endpoints.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | Order ID of a Rotating Mobile proxy order |
| `country` | string | yes | Country code (e.g. "us") |
| `region` | string | yes | Region code from /regions (e.g. "california") |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `cities[].code` | string | City code (lowercase, spaces as "+", e.g. "los+angeles") |
| `cities[].name` | string | City display name |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/mobile-targeting-options/cities?orderId=YOUR_ORDER_ID&country=us&region=california" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"cities": [
  { "code": "los+angeles", "name": "Los Angeles" },
  { "code": "san+francisco", "name": "San Francisco" },
  { "code": "san+diego", "name": "San Diego" }
]
  }
}
```

#### `GET /mobile-targeting-options/isps`

Returns available ISPs for a given country, optionally filtered by region and/or city. Cache by the combination of params used (recommended TTL: 1 hour).

Rate limited to 60 requests per 10 minutes per API key, shared across all 4 mobile targeting endpoints.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `orderId` | string | yes | Order ID of a Rotating Mobile proxy order |
| `country` | string | yes | Country code (e.g. "us") |
| `region` | string | no | Region code to narrow results (optional) |
| `city` | string | no | City code to narrow results (optional) |

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `isps[].code` | string | ISP identifier code |
| `isps[].name` | string | ISP display name |

Request:

```bash
curl -X GET "https://catproxies.com/api/v1/public/mobile-targeting-options/isps?orderId=YOUR_ORDER_ID&country=us&region=california&city=los+angeles" \
  -H "Authorization: Bearer cp_your_api_key_here"
```

Response:

```json
{
  "type": "response",
  "message": "Success",
  "payload": {
"isps": [
  { "code": "at&t+wireless", "name": "at&t wireless" },
  { "code": "t-mobile+usa", "name": "t-mobile usa" },
  { "code": "verizon+wireless", "name": "verizon wireless" }
]
  }
}
```

## White labeling

Every reseller order already returns a **plain IP** as the host, never one of our
hostnames, so nothing in the credentials you pass on points back to us. This section is
for going further and giving your own customers a hostname on YOUR domain.

### Our gateways

| Product | Gateway hostname | Ports |
| --- | --- | --- |
| Standard Residential | `resi-us` / `resi-fr` / `resi-sg` `.catproxies.com` | 9000 HTTP rotating, 10000 HTTP sticky, 11000 / 12000 SOCKS5 |
| Premium Residential | `presix` / `presix-eu` / `presix-asia` (HTTP), `presix-socks` / `presix-eu-socks` / `presix-asia-socks` (SOCKS5), all `.catproxies.com` | 6011 / 6012 / 6013 HTTP, 6014 / 6015 / 6016 SOCKS5 |
| Premium Residential ISP pools | `usax` / `usax-tk` / `ukx` / `dex` / `dex-dtag` / `aux` `.catproxies.com`, or the `isp_pools` IPs on `GET /order/:id` | 6000 / 6001 / 6003 / 6006 / 6007 / 6002, HTTP only |
| Rotating Mobile | `mobile-us` / `mobile-eu` / `mobile-asia` `.catproxies.com` | 5000 |
| IPv6 | `ipv6-ww` / `ipv6-us` / `ipv6-de` / `ipv6-gb` / `ipv6-nl` `.catproxies.com` | 1338 HTTP, 1339 SOCKS5 |
| Datacenter | No hostname. Point your record at the `rotating_gateway_sets` IPs for the country on `GET /order/:id` (for the US, all of them, so it reaches every US IP). | 1338 HTTP, 1339 SOCKS5 |
| Dedicated ISP | No gateway. You get individual IP:port lines, and they are yours for the life of the plan. | As returned |
| Custom ISP | A plain server IP per order, or your own hostname with Auto included: set it up with `POST /custom-isp/domain` (see the Custom ISP guide). Use that instead of a CNAME or A records. | 1337, HTTP and SOCKS5 |

### Two ways to point your domain at us

**CNAME. Never breaks.**

```
proxy.yourdomain.com  CNAME  resi-us.catproxies.com
```

When we move a gateway your hostname follows on its own and you do nothing. The
catch: a DNS lookup on your record shows our hostname, so anyone curious can see
who is behind it.

**A records. Nothing links back.**

```bash
# read the current IPs, then add one A record per IP
dig +short resi-us.catproxies.com

proxy.yourdomain.com  A  192.0.2.10
proxy.yourdomain.com  A  192.0.2.11
```

A lookup shows only IP addresses, so there is nothing to trace. The catch is that it
goes stale: when we rotate a gateway your record points at a dead address and your
customers break until you change it yourself.

**If you go the A record route:** add EVERY IP the hostname resolves to, not just
the first one. Most of our gateways are several nodes behind one name, and pinning a
single IP throws away the failover. Gateway IPs do change, and we announce every
change in our update channel before it happens (Discord https://discord.gg/catproxy
and Telegram https://t.me/catproxies), so follow one and update your records
when we do. If uptime matters to you more than being untraceable, use the CNAME.

## Support

Questions about the API: support@catproxies.com. The human version of this document is at https://catproxies.com/docs
