---
name: proxies-sx-pool-portal
description: Build a branded mobile-proxy reseller business or embed Proxies.sx mobile/residential proxies into any user-facing app. Trigger this skill whenever the user wants to resell proxies, mint per-customer access keys (pak_*), embed a proxy dashboard into their site, deploy a Next.js proxy storefront, integrate the Proxies.sx Pool Gateway, build a customer-facing proxy product, or add 4G/5G mobile proxies to ANY stack - JavaScript, TypeScript, React, Next.js, Python, PHP, Ruby, Go, Rust, or plain curl. Use it the moment the user says "mobile proxy", "proxy reseller", "pak_ keys", "pool gateway", "proxy dashboard", "embed proxies", "white-label proxy", or anything implying customer-facing proxy delivery - even if they don't explicitly ask for "this skill".
---
# Proxies.sx Pool Portal - Build and Resell
**What this is:** an open-source toolkit for reselling [Proxies.sx Pool Gateway](https://client.proxies.sx/pool-proxy) proxies under your own brand, or embedding them into any user-facing app. You mint one `pak_` key per paying customer; that key is the **password** half of a proxy URL against `gw.proxies.sx:7000` (HTTP) / `7001` (SOCKS5).
**Why:** you set your own retail price. Wholesale rates are volume-tiered and change - read them live from `GET https://api.proxies.sx/v1/billing/pricing` (with your `psx_` API key - the account-path source of truth) or the public no-auth mirror `GET https://api.proxies.sx/v1/x402/pricing`. Never hardcode them.
**If this file disagrees with the live API, the live API wins.**
> **Rotation cookbook** (copy-paste IP rotation in curl/python/node + the footguns that trip LLMs): https://agents.proxies.sx/pool/rotation-cookbook.md
## First request
Requires a reseller `psx_` API key (see Prerequisites below).
```bash
curl -s -X POST "https://api.proxies.sx/v1/reseller/pool-keys" \
-H "X-API-Key: psx_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"label":"customer:alice@example.com","trafficCapGB":10}'
```
Response:
```json
{
"id": "65f...",
"key": "pak_a1b2c3...",
"label": "customer:alice@example.com",
"trafficCapGB": 10,
"trafficUsedGB": 0,
"enabled": true,
"createdAt": "..."
}
```
Then:
1. **Persist two fields:** `id` (your management handle) and `key` (the `pak_` secret the customer will use).
2. **Build the proxy URL** the customer plugs into their HTTP client (grammar below): `http://RESELLER_USERNAME-mbl-us:pak_a1b2c3@gw.proxies.sx:7000`
3. **Verify it routes:** `curl -x "http://RESELLER_USERNAME-mbl-us:pak_a1b2c3@gw.proxies.sx:7000" https://api.ipify.org` should return a US mobile IP.
4. **Pick an integration path** (A/B/C/D below) for the production build.
## The three layers
1. **`@proxies-sx/pool-sdk`** - typed REST client (npm). For JS/TS code that mints `pak_` keys, lists usage, and builds proxy URLs.
2. **`@proxies-sx/pool-portal-react`** - drop-in `` component + headless hooks + `createPoolApiHandlers()` Next.js route factory.
3. **REST API** - language-agnostic. Anyone with an HTTP client (Python, PHP, Go, Ruby, bash + curl, ...) can integrate.
Source: . License: MIT.
---
## When to use this skill
Use it for any of these intents:
- "I want to resell mobile proxies under my own brand"
- "Embed a proxy dashboard in my customer portal"
- "Mint a sub-key per paying customer"
- "Add proxy access to my [SaaS / scraping shop / ad platform]"
- "Wire Proxies.sx into my [Next.js / React / Vue / PHP / Python / Go] app"
- "I want a Stripe-paid proxy storefront"
- "Build a `pak_` key minting flow"
If the user mentions Pool Gateway, `pak_` keys, `psx_` reseller keys, `gw.proxies.sx:7000`, or any of the SDK/component names above, this skill applies.
---
## The three credentials (know which is which)
| Credential | Format | Sensitivity | Where it lives |
|---|---|---|---|
| Reseller API key | `psx_...` (from the API keys page) | **Secret. Server-side only.** Mints and manages `pak_` keys. | Env var / secrets manager, never the browser |
| Reseller username | `psx_` | Public. It is the username half of every customer proxy URL. | Safe in client code and proxy URLs |
| pak key | `pak_...` | Per-customer secret. It is the **password** half of that customer's proxy URL. | Your DB; hand to exactly one customer; rotate via regenerate |
---
## Decide the integration path FIRST
Before writing code, ask which path they want. Don't guess - the implementation differs significantly.
```
Q1: Does the user already have an app, or are they starting fresh?
Starting fresh
-> PATH A: clone the full Next.js starter storefront (apps/starter/)
Existing app
-> Q2: What stack is it?
React / Next.js
-> PATH B: embed the component
Anything else
-> Q3: Is the backend JavaScript/TypeScript?
JS/TS backend (Express, Fastify, Hono, Bun, Workers, ...)
-> PATH C: SDK only, no UI
Not JS (PHP, Python, Go, Ruby, ...)
-> PATH D: direct REST API, any language
```
Confirm the choice with the user before generating code. Each path follows.
---
## Prerequisites (all paths)
The user needs ONE thing first: a Proxies.sx reseller API key.
- Sign up / log in at [client.proxies.sx](https://client.proxies.sx)
- Visit [client.proxies.sx/account](https://client.proxies.sx/account)
- Click "Create API key" with scope `customers:write`
- Save the `psx_...` value - **server-side only, never expose to the browser**
The user will also have a "reseller username" of the form `psx_` shown in the same dashboard. That value is safe to reference in proxy URLs (it's the public part of the proxy auth) - it's NOT the secret API key.
If the user doesn't have an API key yet, instruct them to mint one before any code runs. Don't try to mock it.
### Quality tier - decide once, set per customer key
Every `pak_` key carries a `qualityTier`, chosen at mint time:
- `qualityTier: 'safe'` -> the gateway silently rewrites `peer-*` pool tokens in that customer's proxy URL to `mbl-*`. The customer always lands on a production modem (production-grade, 10-15 Mbps, sub-1s TTFB). Use for paying customers with strict SLAs.
- `qualityTier: 'standard'` (default) -> routes across both pools by health and load. Cheaper average, more variance. Use for hobby / best-effort use cases.
Set it via `POST /v1/reseller/pool-keys` with `{"qualityTier":"safe"}` in the body. Default is `standard`. Every rewrite is logged to the pak audit trail, so the reseller can see when it fired.
---
## PATH A - Deploy the full Next.js storefront
Use when the user wants a **complete branded reseller site** (landing page, magic-link login, Stripe checkout, customer dashboard) and is starting from scratch.
```bash
git clone https://github.com/bolivian-peru/proxy-reseller-kit.git my-shop
cd my-shop/apps/starter
cp .env.example .env
# Edit .env: PROXIES_SX_API_KEY, PROXIES_SX_USERNAME, STRIPE_SECRET_KEY,
# STRIPE_WEBHOOK_SECRET, AUTH_SECRET, DATABASE_URL
pnpm install
docker compose up -d db # local Postgres on :5432
pnpm db:migrate # idempotent schema bootstrap
pnpm dev # -> http://localhost:3000
```
In another terminal:
```bash
stripe listen --forward-to localhost:3000/api/stripe/webhook
```
**What you get out of the box:**
- `/` - landing + pricing tiers (configured in `src/config.ts`)
- `/login` - NextAuth (Auth.js v5) magic-link auth (in dev, the link prints to server console - no SMTP required)
- `/dashboard` - `` showing the customer's `pak_` key, country selector, copy-to-clipboard proxy URLs
- `/api/stripe/checkout` + `/api/stripe/webhook` - Stripe checkout that mints a `pak_` key on payment success
- `/api/pool/[...path]` - proxies SDK calls server-side (keeps `psx_` key off the client)
**Customize:** edit `apps/starter/src/config.ts`:
```ts
export const config = {
brand: { name: 'AcmeProxies', primaryColor: '#7c3aed', supportEmail: '...' },
pricing: [
{ id: 'starter', displayName: 'Starter', gb: 5, priceUsd: 35 },
{ id: 'pro', displayName: 'Pro', gb: 25, priceUsd: 150 },
{ id: 'scale', displayName: 'Scale', gb: 100, priceUsd: 500 },
],
countries: ['us', 'nl', 'pl', 'fr', 'ge', 'gb'], // as-of example - check live stock
};
```
Check live per-country stock at `GET https://api.proxies.sx/v1/gateway/pool/stock` before choosing retail countries - the set shifts with supply, and some countries (e.g. de, es) currently have peer-pool coverage only while the starter defaults to the mbl pool.
**Deploy:** `docker compose up --build -d` on a VPS with Caddy/nginx terminating TLS in front.
Full per-task guide: see `apps/starter/CLAUDE.md` in the repo.
---
## PATH B - Embed `` in an existing React/Next.js app
Use when the user already has auth, billing, and a UI shell, and just wants to drop a proxy dashboard onto a page.
```bash
npm install @proxies-sx/pool-portal-react @proxies-sx/pool-sdk
```
**Two pieces:** the component (client) + an API route (server, holds the secret).
### 1. Server route (Next.js App Router example)
`app/api/pool/[...path]/route.ts`:
```ts
import { createPoolApiHandlers } from '@proxies-sx/pool-portal-react/server';
import { ProxiesClient } from '@proxies-sx/pool-sdk';
import { auth } from '@/lib/auth'; // your existing auth
import { db } from '@/lib/db';
const handlers = createPoolApiHandlers({
proxies: new ProxiesClient({
apiKey: process.env.PROXIES_SX_API_KEY!,
proxyUsername: process.env.PROXIES_SX_USERNAME!,
}),
// CRITICAL: scope each request to the logged-in user so customer A
// can never see customer B's keys.
getSessionUserId: async () => (await auth())?.user?.id ?? null,
getUserKeyId: async (userId) => (await db.customers.get(userId))?.pakKeyId ?? null,
});
export const GET = handlers.GET;
export const POST = handlers.POST;
export const DELETE = handlers.DELETE;
```
### 2. Page
`app/dashboard/page.tsx`:
```tsx
'use client';
import { PoolPortal } from '@proxies-sx/pool-portal-react';
import '@proxies-sx/pool-portal-react/styles.css';
export default function Dashboard() {
return (
);
}
```
For non-Next.js React apps (CRA, Vite, Remix, etc.), implement the same handlers in your own backend framework - see headless hooks below for finer control.
### Headless hooks (custom UI)
If `` doesn't fit your design, use the hooks directly:
```tsx
import { usePoolKey, usePoolStock, useIncidents, useCopyToClipboard } from '@proxies-sx/pool-portal-react';
const { data: me, loading, refetch } = usePoolKey('/api/pool'); // GET {apiRoute}/me
const { data: stock } = usePoolStock('/api/pool'); // GET {apiRoute}/stock
// me.pakKey, me.usage.usedGB, me.usage.capGB - all hooks return { data, loading, error, refetch }
// rotate the key via POST {apiRoute}/regenerate
```
---
## PATH C - Just the SDK (any JS/TS server)
Use when the user has a non-React frontend (Vue, Svelte, plain HTML) but a JS backend (Express, Fastify, Hono, Bun, Cloudflare Workers, ...).
```bash
npm install @proxies-sx/pool-sdk
```
```ts
import { ProxiesClient } from '@proxies-sx/pool-sdk';
// Server-side ONLY. Never bundle PROXIES_SX_API_KEY into a browser build.
const proxies = new ProxiesClient({
apiKey: process.env.PROXIES_SX_API_KEY!,
proxyUsername: process.env.PROXIES_SX_USERNAME!,
});
// 1. Mint a key for a customer who just paid
const key = await proxies.poolKeys.create({
label: `customer:${customerId}`,
trafficCapGB: 10, // null/omit = unlimited within reseller's pool
});
// 2. Store key.id (for management) and key.key (the pak_ secret) in your DB
await db.update(customerId, { pakKeyId: key.id, pakKey: key.key });
// 3. Build the proxy URL the customer uses in their HTTP client
const proxyUrl = proxies.buildProxyUrl(key.key, {
country: 'us',
sid: customerId, // sticky session - same customer = same exit IP
rotation: 'sticky',
});
// -> "http://psx_abc-mbl-us-sid-cust_7a3f9b-rot-sticky:pak_xyz@gw.proxies.sx:7000"
```
**Other operations:**
```ts
await proxies.poolKeys.list(); // list all keys with usage + isExpired flag
await proxies.poolKeys.update(keyId, { label }); // update label / cap / enabled / expiresAt
await proxies.poolKeys.topUp(keyId, { addTrafficGB: 10, idempotencyKey }); // atomic top-up (cap and/or expiry), retry-safe
await proxies.poolKeys.regenerate(keyId); // rotate the secret (backend rejects old pak_ instantly; gateway stops accepting it within ~30s auth-cache TTL)
await proxies.poolKeys.delete(keyId); // permanent
await proxies.pool.getStock(); // live endpoint count by country
await proxies.pool.getIncidents(); // active pool incidents
```
**Time-bounded credits with `expiresAt` (v0.2.0+):**
```ts
// Mint a 60-day "use it or lose it" credit
const key = await proxies.poolKeys.create({
label: 'customer:alice',
trafficCapGB: 10,
expiresAt: new Date(Date.now() + 60 * 86_400_000).toISOString(),
});
// On top-up, bump cap AND push expiry forward in one call
await proxies.poolKeys.update(key.id, {
trafficCapGB: 25,
expiresAt: new Date(Date.now() + 60 * 86_400_000).toISOString(),
});
// Remove the expiry (perpetual key)
await proxies.poolKeys.update(key.id, { expiresAt: null });
// Helpers
import { isPoolKeyExpired, daysUntilPoolKeyExpiry } from '@proxies-sx/pool-sdk';
isPoolKeyExpired(key); // boolean
daysUntilPoolKeyExpiry(key); // number | null
```
The gateway rejects expired keys **immediately** (no wait for the nightly cron). The platform's daily cron (03:30 UTC) sets `enabled=false` on past-expiry keys for tidier admin queries.
---
## PATH D - Direct REST API (PHP / Python / Go / Ruby / any language)
Use when the user's backend is **not JavaScript**. The SDK is a thin wrapper around a public REST API - anyone with an HTTP client can integrate.
**Auth:** `X-API-Key: psx_...` header on every request.
**Endpoints:**
| Method | Path | Purpose |
|---|---|---|
| `POST` | `/v1/reseller/pool-keys` | Mint a `pak_` key (accepts optional `expiresAt` ISO datetime; optional `Idempotency-Key` header dedupes retries for 24h so a retried webhook can't double-mint) |
| `GET` | `/v1/reseller/pool-keys` | List keys + usage (returns `expiresAt`, server-computed `isExpired`) |
| `PATCH` | `/v1/reseller/pool-keys/{keyId}` | Update `label` / `enabled` / `trafficCapGB` / `expiresAt` |
| `POST` | `/v1/reseller/pool-keys/{keyId}/topup` | Atomic top-up: `addTrafficGB` and/or `extendDays` (optional `Idempotency-Key` header; prefer over PATCH for credit extensions) |
| `POST` | `/v1/reseller/pool-keys/{keyId}/regenerate` | Rotate secret (backend rejects the old value instantly; gateway stops accepting it within ~30s auth-cache TTL) |
| `DELETE` | `/v1/reseller/pool-keys/{keyId}` | Delete permanently |
Base URL: `https://api.proxies.sx/v1`. Public docs: Pool Gateway Swagger + Reseller OpenAPI . (The full customer Swagger at /docs/api is basic-auth gated - not public.)
### Mint a key - minimum viable curl
```bash
curl -X POST "https://api.proxies.sx/v1/reseller/pool-keys" \
-H "X-API-Key: psx_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"label":"customer:alice@example.com","trafficCapGB":10}'
# Response:
# {
# "id": "65f...",
# "key": "pak_a1b2c3...",
# "label": "customer:alice@example.com",
# "trafficCapGB": 10,
# "trafficUsedGB": 0,
# "enabled": true,
# "createdAt": "..."
# }
```
Persist `id` (management handle) and `key` (the `pak_` secret).
### Per-language patterns
**Python:**
```python
import requests
resp = requests.post(
"https://api.proxies.sx/v1/reseller/pool-keys",
headers={"X-API-Key": "psx_YOUR_API_KEY"},
json={"label": "customer:alice", "trafficCapGB": 10},
)
key = resp.json()["key"] # "pak_..."
```
**PHP:**
```php
$ch = curl_init('https://api.proxies.sx/v1/reseller/pool-keys');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: psx_YOUR_API_KEY',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['label' => 'customer:alice', 'trafficCapGB' => 10]),
]);
$key = json_decode(curl_exec($ch), true)['key']; // pak_...
```
**Go:**
```go
body := strings.NewReader(`{"label":"customer:alice","trafficCapGB":10}`)
req, _ := http.NewRequest("POST", "https://api.proxies.sx/v1/reseller/pool-keys", body)
req.Header.Set("X-API-Key", "psx_YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
```
**Ruby:**
```ruby
require 'net/http'; require 'json'
uri = URI('https://api.proxies.sx/v1/reseller/pool-keys')
req = Net::HTTP::Post.new(uri, 'X-API-Key' => 'psx_YOUR_API_KEY', 'Content-Type' => 'application/json')
req.body = { label: 'customer:alice', trafficCapGB: 10 }.to_json
resp = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
```
---
## The proxy URL grammar (every path uses this)
The customer's HTTP/SOCKS5 client connects to:
```
{protocol}://{username}:{pakKey}@gw.proxies.sx:{port}
```
| Field | Value |
|---|---|
| `protocol` | `http` or `socks5` |
| `port` | `7000` for HTTP, `7001` for SOCKS5 |
| `username` | `psx_RESELLER_USERNAME` + routing fields, `-`-separated |
| `pakKey` | The `pak_*` secret minted via the API - it goes in the **password** field, never in the username |
**Username structure:** the reseller username comes first, then **pool** and **country** as positional routing fields, then optional named `token-value` pairs in any order:
| Position / token | Example | Meaning |
|---|---|---|
| Pool | `mbl`, `peer` | `mbl` = production mobile modems, `peer` = residential peer devices. The SDK's `buildProxyUrl` uses `mbl` when unspecified. |
| Country | `us`, `nl`, `pl`, `fr`, `ge`, `gb` | ISO 3166-1 alpha-2 (as-of examples - live set at `GET /v1/gateway/pool/stock`) |
| `sid-{id}` | `sid-alice_session1` | Sticky session - same `sid` keeps the same exit endpoint across connections. No `sid` = no cross-connection stickiness. |
| `rot-{mode}` | `rot-sticky`, `rot-hard`, `rot-auto5`, `rot-auto10`, `rot-auto20`, `rot-auto60`, `rot-ondemand` | IP rotation policy |
| `city-{name}` | `city-nyc` | City filter (when supported) |
| `carrier-{name}` | `carrier-att`, `carrier-tmobile` | Carrier filter |
**Example URL:**
```
http://psx_acme-mbl-us-sid-customer123-rot-sticky:pak_a1b2c3@gw.proxies.sx:7000
```
This says: route customer123's traffic through US mobile modems, keep the same exit endpoint for the session.
**Sharp edge to teach your customers:** sticky pins the **modem**, not the IP. Mobile carriers re-issue NAT egress IPs on their own schedule, so a pinned modem can still surface different exit IPs across requests. That is carrier behavior, not a routing failure.
The SDK's `buildProxyUrl(pakKey, opts)` generates the URL. In other languages, build the string manually:
```python
def build_proxy_url(reseller, pak_key, country='us', sid=None, rotation='sticky'):
parts = [reseller, 'mbl', country]
if sid: parts.append(f'sid-{sid}')
if rotation: parts.append(f'rot-{rotation}')
return f"http://{'-'.join(parts)}:{pak_key}@gw.proxies.sx:7000"
```
---
## Error handling
The REST API returns standard HTTP codes. Map them like this:
| Status | Meaning | Action |
|---|---|---|
| `200` / `201` | Success | Use the response body |
| `400` | Validation error | Show error details to the user, don't retry |
| `401` | API key invalid or revoked | Re-mint key from `client.proxies.sx/account` |
| `403` | Scope insufficient | Add `customers:write` to the key |
| `404` | Key doesn't exist | Stop - don't loop |
| `429` | Rate-limited | Back off (exponential, start at 1s) |
| `500-599` | Server error | Retry up to 3x with exponential backoff |
The SDK ships these as typed errors:
```ts
import { ProxiesApiError, ProxiesTimeoutError } from '@proxies-sx/pool-sdk';
try { await proxies.poolKeys.create({ label: 'x' }); }
catch (err) {
if (err instanceof ProxiesApiError) {
if (err.isAuth) { /* 401/403 */ }
if (err.isRateLimited) { /* 429 */ }
if (err.isServer) { /* 5xx */ }
} else if (err instanceof ProxiesTimeoutError) {
/* request exceeded timeout */
}
}
```
Gateway-side errors (from `gw.proxies.sx` itself) come back as `CODE: message (req: uuid)`. Support: maya@proxies.sx or https://t.me/proxies_sx - quote the req id.
---
## Security non-negotiables
DO NOT skip these. Burn them into any code generated for the user:
1. **`PROXIES_SX_API_KEY` is server-only.** Never inline it in `next.config.js`, never `NEXT_PUBLIC_*` it, never ship it to the browser bundle. The trust boundary lives at your backend.
2. **Scope every request** by the authenticated customer. In the React PATH B example, `getSessionUserId` MUST read the session - without it, customer A can read/regenerate customer B's keys.
3. **Use parameterized SQL** if you're storing keys (the starter app does this - `$1`, `$2` placeholders, never string interpolation).
4. **Verify Stripe webhook signatures.** The starter app's webhook handler does this; if you adapt it, do not comment out the signature check "to test".
5. **Rotate leaked `pak_` keys immediately** via `regenerate()` - the backend rejects the old value instantly; the gateway stops accepting it within ~30 seconds (auth-cache TTL).
6. **Store `psx_` keys in a secrets manager**, not in source. The starter uses `.env`; production deployments should use 1Password / Doppler / AWS Secrets Manager / etc.
---
## Common patterns
### Customer pays -> mint key (Stripe webhook)
```ts
// Pseudocode - works in any framework
async function onStripeCheckoutCompleted(event) {
const session = event.data.object;
const customerId = session.client_reference_id;
const gbPurchased = Number(session.metadata.gb);
const key = await proxies.poolKeys.create({
label: `customer:${customerId}`,
trafficCapGB: gbPurchased,
idempotencyKey: event.id, // Stripe retries webhooks - dedupe so a retry can't double-mint
});
await db.update(customerId, { pakKeyId: key.id, pakKey: key.key });
}
```
### Customer wants to rotate their own credentials
```ts
async function rotateForCustomer(customerId) {
const customer = await db.get(customerId);
const { id, key } = await proxies.poolKeys.regenerate(customer.pakKeyId);
await db.update(customerId, { pakKey: key });
return key; // hand to UI
}
```
### Show usage on dashboard
```ts
const keys = await proxies.poolKeys.list();
const ours = keys.find(k => k.id === customer.pakKeyId);
console.log(`${ours.trafficUsedGB} / ${ours.trafficCapGB ?? 'unlimited'} GB used`);
```
### Top-up: customer pays for more, increase the cap
```ts
// Atomic server-side increment - no read-modify-write race
await proxies.poolKeys.topUp(customer.pakKeyId, {
addTrafficGB: additionalGB,
idempotencyKey: `topup_${invoiceId}`, // retries can't double-credit
});
```
---
## Pricing (do NOT pin numbers in code or docs)
Wholesale rates from Proxies.sx have volume tiers and are configured by the platform - they can change at any time. Do not hardcode dollar amounts in the user's app.
To get current rates programmatically:
- `GET https://api.proxies.sx/v1/billing/pricing` (with the reseller's `psx_` API key, scope `billing:read` - the account-path source of truth, includes volume discounts + tier info)
- `GET https://api.proxies.sx/v1/x402/pricing` (public, no auth - convenient mirror when no key is at hand)
- Or check the user's [client.proxies.sx](https://client.proxies.sx) dashboard
The user sets their **retail** price (whatever they charge their own customers) - that lives in their own app config (`apps/starter/src/config.ts` in the starter, or wherever they put it). The wholesale price affects their margin, not their pricing UI.
---
## Reference files (deeper detail)
When the user needs more than this skill provides, point them to the right file in the repo:
| File | When to read |
|---|---|
| `README.md` | Marketing-friendly overview, FAQ, license |
| `packages/sdk/README.md` | Full SDK API surface, all methods, error types, language-by-language REST examples |
| `packages/react/README.md` | `` props, all hooks, server handler details, theming |
| `apps/starter/README.md` | Full Next.js storefront deployment guide |
| `apps/starter/CLAUDE.md` | Per-task instructions for AI agents customizing the starter (change brand, add country, add admin page, change DB schema) |
| `CLAUDE.md` | Repo-wide architecture + invariants for AI agents working ON the SDK code itself |
| `SECURITY.md` | Production hardening checklist |
---
## Quick smoke test (run before reporting "done" to the user)
After generating code, verify the integration works end-to-end. Don't trust types alone.
```bash
# 1. Mint a real key (replace YOUR_KEY with the user's real psx_ key)
RESPONSE=$(curl -s -X POST "https://api.proxies.sx/v1/reseller/pool-keys" \
-H "X-API-Key: psx_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"label":"smoke-test","trafficCapGB":1}')
# pak_ keys are "pak_" + lowercase hex, so this grep extraction is safe to port to other shells
PAK=$(echo "$RESPONSE" | grep -oE 'pak_[a-z0-9]+')
echo "Got key: $PAK"
# 2. Use it through the gateway (USERNAME = the user's psx_ reseller username)
curl -x "http://USERNAME-mbl-us:$PAK@gw.proxies.sx:7000" https://api.ipify.org
# Should return a US mobile IP.
# 3. Clean up
KEY_ID=$(echo "$RESPONSE" | grep -oE '"id":"[^"]+"' | cut -d'"' -f4)
curl -X DELETE "https://api.proxies.sx/v1/reseller/pool-keys/$KEY_ID" \
-H "X-API-Key: psx_YOUR_KEY"
```
If step 2 returns a real US IP via the proxy, the integration works.
---
## Final checklist before handing off
- [ ] User has a `psx_*` API key from `client.proxies.sx/account`
- [ ] The key is in a server-side env var, never in client code
- [ ] If using PATH B/C, the API route scopes by authenticated user
- [ ] Stripe webhook signature is verified (if using Stripe)
- [ ] User can mint a `pak_*`, build a proxy URL, and route real traffic through it
- [ ] User knows how to regenerate a leaked `pak_*`
- [ ] No specific pricing numbers were hardcoded - UI either reads live pricing (`/v1/billing/pricing` with the reseller key, or public `/v1/x402/pricing`) or shows the user's own retail tiers