Skip to content

Integration Paths

peruvian edited this page Jul 2, 2026 · 3 revisions

Integration Paths

The kit supports four integration shapes. Pick one based on what you already have. Mixing paths is rarely a good idea — they have different boundaries and trust models.

Decision tree

Do you have an app already?
│
├── No / starting fresh
│   └──▶ PATH A — clone apps/starter/ (full Next.js storefront)
│
└── Yes
    │
    ├── It's a React/Next.js app
    │   └──▶ PATH B — <PoolPortal /> drop-in component
    │
    ├── It's a non-React JS app (Express, Fastify, Hono, Bun, Deno, Workers)
    │   └──▶ PATH C — @proxies-sx/pool-sdk only
    │
    └── It's not JavaScript at all (PHP, Python, Go, Ruby, Rust, Elixir, etc.)
        └──▶ PATH D — REST API directly

PATH A — Full Next.js storefront (recommended for greenfield)

You get: a complete app — landing page, pricing, magic-link auth, Stripe checkout, customer dashboard, webhook handler, Postgres schema — all under 1,000 LOC.

Effort: ~10 minutes to running locally with test Stripe.

Use when: you don't already have a customer-facing app and want to get to first revenue fast.

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, STRIPE_*, AUTH_SECRET, EMAIL settings
pnpm install
docker compose up -d db
pnpm db:migrate
pnpm dev

In another terminal:

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

Visit http://localhost:3000. Sign in (dev mode prints the magic link to your terminal — no SMTP needed). Buy a plan with 4242 4242 4242 4242. Webhook mints a pak_. Dashboard shows your live proxy URL.

Customize everything in apps/starter/src/config.ts — brand name, colors, logo, pricing, supported countries, CTA copy.

Deploy to anywhere that runs Node + Postgres: Vercel + Neon, Fly.io, Railway, your own VPS with Docker Compose. All examples documented in apps/starter/README.md.


PATH B — Embed <PoolPortal /> in an existing React app

You get: a drop-in customer dashboard component that handles pak display, usage stats, proxy URL builder, session spawner. Plus a createPoolApiHandlers() route factory for the backend.

Effort: ~15 minutes if your auth and routing are already wired.

Use when: you have a Next.js / Remix / Vite-React app and want to add a proxy dashboard page without rebuilding it.

npm install @proxies-sx/pool-portal-react @proxies-sx/pool-sdk
# npm serves the current releases (pool-portal-react 0.9.0, pool-sdk 0.8.1) -
# the GitHub Release tarball workaround from the 0.6.x era is no longer needed

Frontend — drop in a page:

// app/dashboard/page.tsx
'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' }} />;
}

Backend — one catchall route:

// app/api/pool/[...path]/route.ts
import { ProxiesClient } from '@proxies-sx/pool-sdk';
import { createPoolApiHandlers } from '@proxies-sx/pool-portal-react/server';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db'; // maps userId -> pakKeyId

const proxies = new ProxiesClient({
  apiKey: process.env.PROXIES_SX_API_KEY!,
  proxyUsername: process.env.PROXIES_SX_USERNAME!,
});

export const { GET, POST, DELETE } = createPoolApiHandlers({
  proxies,
  // Who is making this request? null = handlers return 401.
  getSessionUserId: async () => (await auth())?.user?.id ?? null,
  // Scope each authenticated customer to their own pak. CRITICAL - see below.
  getUserKeyId: async (userId) => (await db.customers.get(userId))?.pakKeyId ?? null,
});

The getSessionUserId + getUserKeyId callbacks are your security boundary. The first resolves who is asking (your auth system); the second maps that user to the pak_ they own (your DB). Without them, /me and /regenerate are unscoped — any logged-in user could touch any pak. Do not skip this.

For full multi-tenant scoping (sessions, etc.), see packages/react/README.md → "Session routes — multi-tenant security."


PATH C — SDK only

You get: a typed TS/JS client. Mint paks, list, update, top-up, regenerate, delete, build proxy URLs.

Effort: ~10 minutes.

Use when: you're on Node/Bun/Deno/Workers and either you're not using React, or you want to build your own UI.

npm install @proxies-sx/pool-sdk
import { ProxiesClient } from '@proxies-sx/pool-sdk';

const proxies = new ProxiesClient({
  apiKey: process.env.PROXIES_SX_API_KEY!,
  proxyUsername: process.env.PROXIES_SX_USERNAME!,
});

// Mint a 10 GB key that lasts 60 days
const key = await proxies.poolKeys.create({
  label: 'customer:alice@example.com',
  trafficCapGB: 10,
  expiresAt: new Date(Date.now() + 60 * 86_400_000).toISOString(),
  idempotencyKey: stripeEvent.id,  // critical for webhook handlers
});

// Build a proxy URL the customer can use
const url = proxies.buildProxyUrl(key.key, {
  country: 'us',
  rotation: 'sticky',
  sid: 'alice_session_1',
});

console.log(url);
// → http://psx_yourUsername-mbl-us-sid-alice_session_1-rot-sticky:pak_xxx@gw.proxies.sx:7000

Full SDK API surface in packages/sdk/README.md.


PATH D — REST API in any language

You get: direct access to the same endpoints the SDK wraps. Plain HTTP, JSON, X-API-Key auth.

Effort: ~5 minutes per language.

Use when: your backend is PHP, Python, Go, Ruby, Rust, Elixir — anything that's not JavaScript.

Endpoint reference

Method Path Purpose
POST /v1/reseller/pool-keys Mint a pak (accepts Idempotency-Key header)
GET /v1/reseller/pool-keys List paks + usage
GET /v1/reseller/pool-keys/:id Fetch one pak
PATCH /v1/reseller/pool-keys/:id Update label / cap / enabled / expiresAt
POST /v1/reseller/pool-keys/:id/topup Atomic cap + expiry extension
POST /v1/reseller/pool-keys/:id/regenerate Rotate the secret
DELETE /v1/reseller/pool-keys/:id Delete

Full OpenAPI spec: api.proxies.sx/docs/api-json · Swagger UI: api.proxies.sx/docs/api

Per-language one-liners

# bash + curl
curl -X POST https://api.proxies.sx/v1/reseller/pool-keys \
  -H "X-API-Key: $PROXIES_SX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $STRIPE_EVENT_ID" \
  -d '{"label":"alice","trafficCapGB":10,"expiresAt":"2026-08-30T00:00:00Z"}'
# Python
import os, requests
r = requests.post(
    'https://api.proxies.sx/v1/reseller/pool-keys',
    headers={
        'X-API-Key': os.environ['PROXIES_SX_API_KEY'],
        'Idempotency-Key': stripe_event_id,
    },
    json={'label': 'alice', 'trafficCapGB': 10},
)
r.raise_for_status()
key = r.json()
// PHP
<?php
$ch = curl_init('https://api.proxies.sx/v1/reseller/pool-keys');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'X-API-Key: ' . getenv('PROXIES_SX_API_KEY'),
    'Content-Type: application/json',
    'Idempotency-Key: ' . $stripeEventId,
  ],
  CURLOPT_POSTFIELDS => json_encode(['label' => 'alice', 'trafficCapGB' => 10]),
  CURLOPT_RETURNTRANSFER => true,
]);
$key = json_decode(curl_exec($ch), true);
// Go
body, _ := json.Marshal(map[string]any{"label": "alice", "trafficCapGB": 10})
req, _ := http.NewRequest("POST", "https://api.proxies.sx/v1/reseller/pool-keys", bytes.NewReader(body))
req.Header.Set("X-API-Key", os.Getenv("PROXIES_SX_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", stripeEventID)
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' => ENV['PROXIES_SX_API_KEY'],
  'Content-Type' => 'application/json',
  'Idempotency-Key' => stripe_event_id,
})
req.body = { label: 'alice', trafficCapGB: 10 }.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }

The proxy URL the customer uses is the same plain HTTP Basic-Auth format regardless of how the pak was minted:

http://psx_RESELLER-mbl-us-sid-CUSTOMER_SESSION-rot-sticky:pak_CUSTOMER_KEY@gw.proxies.sx:7000

Cross-path concerns

These apply no matter which path you pick:

  • Idempotency-Key on every mint — critical for webhook handlers. Use the upstream payment system's event ID, not a random UUID.
  • Never expose psx_ keys to the browser — server-side only.
  • Customer onboarding UX — they need to know the username token format, not just the pak. See "Show your customers HOW to use their pak" in the README.
  • Top up your wholesale balance — see Getting-Started step 1.
  • Pak lifecycle — what happens at expiry, at cap, on regenerate. See Pak-Key-Lifecycle.
  • Sticky semantics — read Sticky-Sessions-and-Rotation before responding to "the IP changes" tickets.

Next reads