BUILD & RESELL

Embed Proxies.sx mobile / residential proxies into any app. SDK, drop-in React component, full Next.js storefront, language-agnostic REST API. Ship a branded reseller business in an afternoon.
4 integration paths
2 npm packages
any language via REST
MIT licensed

For AI agents / code builders: read skill.md first — it has the full decision tree, per-language code patterns, and security checklist in Anthropic skill format. | GitHub: bolivian-peru/pool-starter

WHY THIS EXISTS

Traditional proxy resale means buying modem hardware, juggling SIM plans, running a farm, and paying a developer to build the customer-facing layer. Every supplier price hike eats your margin.

The Proxies.sx Pool Gateway takes care of the infrastructure — you get a single endpoint (gw.proxies.sx:7000) backed by real 4G/5G mobile and residential proxies in 6 countries (DE, PL, US, FR, ES, GB), wholesale pricing with volume tiers, and a per-customer sub-key system (pak_*).

This open-source toolkit takes care of the software — typed SDK, drop-in React component, full Next.js storefront, and a language-agnostic REST API. Zero paid dependencies beyond what you choose (SMTP provider, hosting). MIT licensed — fork it, rebrand it, ship it.


PICK YOUR INTEGRATION PATH

Match your stack to a path. The implementation differs significantly between them — don't mix.

If you...UseEffort
Want a branded reseller storefront, starting freshPATH A — Clone the Next.js starter~10 min
Already have a React / Next.js app and want a drop-in dashboardPATH B — <PoolPortal /> component~15 min
Have a non-React JS app (Express, Fastify, Hono, Vue+API, plain Node, Bun, Deno, Workers)PATH C — SDK only~10 min
Backend is PHP / Python / Go / Ruby / Rust / Elixir (anything not JS)PATH D — REST API directly~5 min

Decision tree + step-by-step for each path also in skill.md.


PREREQUISITES (ALL PATHS)

You need one thing: a Proxies.sx reseller API key.

  1. Sign up / log in at client.proxies.sx
  2. Go to client.proxies.sx/account
  3. Click Create API key with scope customers:write
  4. Save the psx_... value — server-side only, never expose to the browser

You'll also see a "reseller username" of the form psx_<id> in the 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.

CredentialFormatWhere it livesSensitivity
Reseller API keypsx_<hash>Your server env (.env, secrets manager)SECRET — never to browser
Reseller usernamepsx_<id>Embedded in proxy URLs, server-knownPublic-ish (proxy basic-auth)
Customer Pool Access Keypak_<hash>Minted via API, stored in your DB, given to customerSECRET — handle like a password

PATH A — DEPLOY THE FULL NEXT.JS STOREFRONT ~10 MIN

Use when you want a complete branded reseller site (landing, pricing, magic-link login, Stripe checkout, customer dashboard) and are starting from scratch.

git clone https://github.com/bolivian-peru/pool-starter.git my-shop
cd my-shop/apps/starter
cp .env.example .env
# Edit .env: PROXIES_SX_API_KEY, PROXIES_SX_USERNAME, STRIPE_*, 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:

stripe listen --forward-to localhost:3000/api/stripe/webhook

What you get out of the box

RoutePurpose
/Landing + pricing tiers (configured in src/config.ts)
/loginNextAuth (Auth.js v5) magic-link auth (in dev, link prints to console — no SMTP needed)
/dashboard<PoolPortal /> showing customer's pak_, country selector, copy-to-clipboard proxy URLs
/api/stripe/checkoutStripe checkout session per pricing tier
/api/stripe/webhookMints pak_ on payment success (signature-verified, idempotent)
/api/pool/[...path]Server-side SDK calls (keeps psx_ off the client)

Customize

Edit one file: apps/starter/src/config.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', 'de', 'pl', 'fr', 'es', 'gb'],
};

Deploy on a VPS with Caddy/nginx in front of localhost:3000 for TLS. Full task-by-task guide for AI agents: apps/starter/CLAUDE.md.


PATH B — EMBED <PoolPortal /> IN AN EXISTING REACT APP ~15 MIN

Use when you already have auth + billing + a UI shell, and just want a proxy dashboard on a page.

npm install @proxies-sx/pool-portal-react @proxies-sx/pool-sdk

1. Server-side route (Next.js App Router)

app/api/pool/[...path]/route.ts:

import { createPoolApiHandlers } from '@proxies-sx/pool-portal-react/server';
import { auth } from '@/lib/auth';

const handlers = createPoolApiHandlers({
  apiKey: process.env.PROXIES_SX_API_KEY!,
  proxyUsername: process.env.PROXIES_SX_USERNAME!,

  // CRITICAL: scope by authenticated user — without this,
  // customer A can read customer B's keys.
  resolveCustomerContext: async () => {
    const session = await auth();
    if (!session?.user?.id) throw new Response('Unauthorized', { status: 401 });
    return { customerId: session.user.id };
  },
});

export const GET = handlers.GET;
export const POST = handlers.POST;
export const PATCH = handlers.PATCH;
export const DELETE = handlers.DELETE;

2. Page (client)

'use client';
import { PoolPortal } from '@proxies-sx/pool-portal-react';
import '@proxies-sx/pool-portal-react/styles.css';

export default function Dashboard() {
  return (
    <PoolPortal
      apiRoute="/api/pool"
      branding={{ name: 'AcmeProxies', primaryColor: '#7c3aed' }}
    />
  );
}

Headless hooks (custom UI)

If <PoolPortal /> doesn't fit your design, use the hooks directly:

import {
  usePoolKey, usePoolStock, useIncidents, useCopyToClipboard,
} from '@proxies-sx/pool-portal-react';

const { key, isLoading, regenerate } = usePoolKey({ apiRoute: '/api/pool' });
const { stock } = usePoolStock({ apiRoute: '/api/pool' });

For non-Next.js React stacks (CRA, Vite, Remix), implement the same handlers in your own backend framework. The hooks make POST/GET/PATCH/DELETE calls to {apiRoute}/keys and {apiRoute}/stock.


PATH C — JUST THE SDK (ANY JS/TS SERVER) ~10 MIN

Use when you have a non-React frontend (Vue, Svelte, plain HTML) but a JS backend (Express, Fastify, Hono, Bun, Cloudflare Workers, Deno).

npm install @proxies-sx/pool-sdk
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!,
});

// 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
});

// Store key.id (for management) and key.key (the pak_ secret)
await db.update(customerId, { pakKeyId: key.id, pakKey: key.key });

// 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-123-rot-sticky:pak_xyz@gw.proxies.sx:7000"

Other operations

MethodDescription
poolKeys.list()List all keys with usage
poolKeys.update(keyId, { label, enabled, trafficCapGB })Update a key
poolKeys.regenerate(keyId)Rotate the secret (old pak_ stops working immediately)
poolKeys.delete(keyId)Permanent delete
pool.getStock()Live endpoint count by country
pool.getIncidents()Active pool incidents

Runtime support: Node 18.17+, Bun, Deno (with npm: specifier), Vercel Edge, Cloudflare Workers. Zero runtime deps. Pass fetch in config if your runtime lacks global fetch.


PATH D — DIRECT REST API (PHP / PYTHON / GO / RUBY / ANY LANGUAGE) ~5 MIN

Use when your 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_... on every request.

Endpoints

MethodPathPurpose
POST/v1/reseller/pool-keysMint a pak_ key. Accepts optional expiresAt (ISO datetime) for time-bounded credits.
GET/v1/reseller/pool-keysList keys + usage. Returns expiresAt + server-computed isExpired flag.
PATCH/v1/reseller/pool-keys/{keyId}Update label / enabled / trafficCapGB / expiresAt. Pass expiresAt: null to remove an existing expiry.
POST/v1/reseller/pool-keys/{keyId}/regenerateRotate secret (old value invalidated immediately)
DELETE/v1/reseller/pool-keys/{keyId}Permanent delete

Base URL: https://api.proxies.sx/v1
Interactive docs: api.proxies.sx/docs/api · OpenAPI 3.0 spec: api.proxies.sx/docs/api-json

Mint a key — minimum viable curl

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": "...",
#   "trafficCapGB": 10,
#   "trafficUsedGB": 0,
#   "enabled": true,
#   "createdAt": "..."
# }

Per-language patterns

Python (with requests)

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_..."

# Use it as a proxy
proxies = {
    "http":  f"http://psx_RESELLER-mbl-us-sid-alice-rot-sticky:{key}@gw.proxies.sx:7000",
    "https": f"http://psx_RESELLER-mbl-us-sid-alice-rot-sticky:{key}@gw.proxies.sx:7000",
}
r = requests.get("https://api.ipify.org", proxies=proxies)

PHP (cURL)

$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

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

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}
FieldValue
protocolhttp or socks5
port7000 for HTTP, 7001 for SOCKS5
usernamepsx_RESELLER_USERNAME + optional --separated tokens
pakKeyThe pak_* secret minted via the API

Tokens inside the username

All optional, in any order, separated by -:

TokenExampleMeaning
Poolmbl, peermbl = ProxySmart mobile modems (default), peer = residential peer devices
Countryus, de, pl, fr, es, gbISO 3166-1 alpha-2
sid-{id}sid-aliceSticky session — same sid keeps the same exit IP
rot-{mode}rot-sticky, rot-auto10, rot-auto30, rot-hard, rot-noneIP rotation policy
city-{name}city-nycCity filter (when supported)
carrier-{name}carrier-attCarrier 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 IP for the session.

Build the URL in any language

The SDK's buildProxyUrl(pakKey, opts) generates this. 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"

COMMON PATTERNS

Customer pays → mint key (Stripe webhook)

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,
  });

  await db.update(customerId, {
    pakKeyId: key.id,
    pakKey: key.key,
  });
}

Customer rotates their own credentials

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

const keys = await proxies.poolKeys.list();
const ours = keys.find(k => k.id === customer.pakKeyId);
console.log(`${ours.trafficUsedGB} / ${ours.trafficCapGB ?? '∞'} GB used`);

Top-up: customer pays for more, increase the cap

await proxies.poolKeys.update(customer.pakKeyId, {
  trafficCapGB: customer.trafficCapGB + additionalGB,
});

Time-bounded credits ("use within 60 days")

Pass expiresAt (ISO datetime or Date) on create / update. Past the expiry, the gateway rejects the key immediately — no waiting for a cron. The platform's daily cron (03:30 UTC) flips enabled=false on past-expiry keys for tidier admin queries.

// Mint with a 60-day expiry
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 — true if past expiry
daysUntilPoolKeyExpiry(key);    // number | null — days remaining

SDK ≥ 0.2.0 required. The <PoolPortal /> component shows an amber banner at <7 days remaining and a red one once expired (just include expiresAt + isExpired in your /api/pool/me response).


ERROR HANDLING

StatusMeaningAction
200 / 201SuccessUse the response body
400Validation errorShow error details to the user, don't retry
401API key invalid or revokedRe-mint key from client.proxies.sx/account
403Scope insufficientAdd customers:write to the key
404Key doesn't existStop — don't loop
429Rate-limitedBack off (exponential, start at 1s)
500599Server errorRetry up to 3× with exponential backoff

The SDK ships these as typed errors:

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 */
  }
}

SECURITY NON-NEGOTIABLES

RuleWhy it matters
PROXIES_SX_API_KEY is server-onlyNever inline in next.config.js, never NEXT_PUBLIC_*, never ship to browser bundle. Trust boundary lives at your backend.
Scope every request by authenticated userIn PATH B, resolveCustomerContext MUST read the session. Without it, customer A reads/regenerates customer B's keys.
Use parameterized SQL if storing keysThe starter app uses $1, $2 placeholders, never string interpolation.
Verify Stripe webhook signaturesThe starter does this. If you adapt it, do not comment out the check "to test".
Rotate leaked pak_ immediatelyCall regenerate(keyId) — the old value is invalidated within ~1 second.
Store psx_ in a secrets managerProduction: 1Password / Doppler / AWS Secrets Manager — not .env in git.

PRICING

Wholesale rates from Proxies.sx have volume tiers. Do not hardcode dollar amounts in your app — they are configured by the platform and can change.

To get current rates...Endpoint
ProgrammaticallyGET /v1/x402/pricing (public, no auth)
Via dashboardclient.proxies.sx

You set your retail price (whatever you charge your own customers) — that lives in your own app config. The wholesale rate affects your margin, not your customer-facing pricing UI.


REFERENCES & LINKS

Repository

ResourceURL
Main repogithub.com/bolivian-peru/pool-starter
SKILL.md (AI agent integration guide)SKILL.md
SDK README (full API surface)packages/sdk/README.md
React component READMEpackages/react/README.md
Starter app READMEapps/starter/README.md
Repo CLAUDE.md (agents working on the SDK)CLAUDE.md
Starter CLAUDE.md (agents customizing the storefront)apps/starter/CLAUDE.md

npm packages

PackageInstall
@proxies-sx/pool-sdknpm i @proxies-sx/pool-sdk
@proxies-sx/pool-portal-reactnpm i @proxies-sx/pool-portal-react

API references

ResourceURL
Swagger UI (interactive)api.proxies.sx/docs/api
OpenAPI 3.0 spec (JSON)api.proxies.sx/docs/api-json
Reseller LLM-friendly referenceapi.proxies.sx/v1/reseller/docs/llm
Live pricingapi.proxies.sx/v1/x402/pricing
Live pool stockapi.proxies.sx/v1/gateway/pool/availability

AI agent skill files

FileURLPurpose
This page's skill/build/skill.mdAnthropic skill format — drop into Claude Code, Cursor, etc.
Master Proxies.sx skill/skill.mdFull infrastructure reference
Marketplace skill/marketplace/skill.mdx402 service catalog
Peer skill/peer/skill.mdBandwidth-sharing reference

Contact

ChannelFor
Telegram @proxyforaiQuick questions, onboarding
Twitter/X @sxproxiesUpdates, support
agents@proxies.sxReseller upgrade requests
GitHub issuesBug reports, feature requests

Proxies.sx — Build & Resell — pool-starter on GitHub | skill.md for AI agents