--- 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