Copy Trading API Routes
Copy trading routes exist in two layers:
| Layer | What it does | Location |
|---|---|---|
Insiders backend /api/copytrade | User-facing subscription management, history, top traders | polyinsiders-backend-v1 |
Trading Tools v2 control-api :9110 | Engine-level subscription CRUD, live PnL, fills, perf actions | /opt/tradingtools-v2/apps/control-api |
The backend /api/copytrade routes proxy to the trading tools v2 control-api for the heavy engine work. The control-api is internal (loopback :9110) and requires a shared Bearer token.
Insiders Backend · /api/copytrade
Codebase: polyinsiders-backend-v1/src/routes/copytrade.routes.ts
Public discovery
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /top-traders | — | Top traders ranked by performance |
| GET | /trader/:address | — | Trader profile + stats |
User subscriptions
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /history | 🔒 | Copy trade execution history |
| POST | /subscriptions | 🔒 | Create a copy subscription |
| GET | /subscriptions | 🔒 | List active subscriptions |
| GET | /subscriptions/:id | 🔒 | Get subscription + live PnL |
| PATCH | /subscriptions/:id | 🔒 | Update subscription settings |
| DELETE | /subscriptions/:id | 🔒 | Cancel subscription |
Trading Tools v2 · control-api :9110
Codebase: /opt/tradingtools-v2/apps/control-api/src/index.ts
Spec: /opt/tradingtools-v2/docs/control_api_wire_spec_v1.md
⚠️
Not browser-callable. Terminal devs call via the backend proxy. The proxy resolves user → FollowerIdentity and attaches Authorization: Bearer <CONTROL_API_TOKEN>.
Call path
Frontend → Insiders API backend → control-api :9110Identity shape (required in every create body)
follower: {
followerUserId: string // backend users.id
followerWalletId: string // Privy custodial wallet id (EOA = 400)
followerProxy: string // 0x… deposit/proxy address
}Subscription endpoints
| Method | Path | Description |
|---|---|---|
| POST | /subscriptions | Create copy subscription → 201 |
| GET | /subscriptions?followerProxy=0x… | List subs + live stats |
| GET | /subscriptions/:id | Single sub + live PnL |
| GET | /subscriptions/:id/fills | Raw copy fills (paged: ?limit=&offset=) |
| PATCH | /subscriptions/:id | Modify sizing / filters / exitPolicy / performanceTriggers / autoSell |
| POST | /subscriptions/:id/pause | Stop copying new trades (positions remain) |
| POST | /subscriptions/:id/resume | Re-arm subscription |
| DELETE | /subscriptions/:id | Stop + remove from hot index (does NOT flatten positions) |
TP/SL trigger endpoints (GUARD)
| Method | Path | Description |
|---|---|---|
| POST | /triggers | Create TP/SL on a copy position → 202 |
| PATCH | /triggers/:id | Modify trigger |
| DELETE | /triggers/:id | Cancel trigger |
| GET | /triggers?wallet=0x… | List triggers for wallet |
| GET | /triggers/:id | Get single trigger |
Internal endpoints (executor / perfmon only)
| Method | Path | Description |
|---|---|---|
| POST | /subscriptions/funding | Flip out-of-funds gate for all of a follower's subs |
| POST | /subscriptions/:id/perf-action | Session PnL action: pause / set_multiplier / resume |
Health
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health | none | Liveness probe |
| GET | /ready | none | Readiness probe |
POST /subscriptions — Full request shape
{
follower: { followerUserId, followerWalletId, followerProxy },
leaderProxy: string, // leader 0x… address
sizing: {
strategy: "fixed" | "proportional" | "capped",
params:
| { usd: number } // fixed
| { pct: number } // proportional (0.5 = 50%)
| { pct: number, minUsd: number, maxUsd: number } // capped
},
autoSell: boolean, // mirror leader's sells
filters?: {
tradeSize?: { minUsd?: number, maxUsd?: number }, // A1
priceBand?: { min: number, max: number }, // A2 (0–1)
side?: "buy" | "sell", // A3
category?: { allow?: string[], exclude?: string[] }, // A4
volumeFloor?: { minUsd: number } // A5
},
exitPolicy?: {
takeProfitPct?: number, // 25 = exit at +25%
stopLossPct?: number,
trailingPct?: number
},
performanceTriggers?: {
lossStopUsd?: number,
profitScale?: { thresholdUsd?: number, thresholdPct?: number, multiplier: number }
},
enrichMissPolicy?: "copy" | "skip" // default "copy"
}Known cosmetic deviations (wire vs types)
cappedsizing reads back as{pct, min, max}not{pct, minUsd, maxUsd}as sent- Mutation responses are bare
{"success": true}— re-GET to read new state SubscriptionViewincludesisFunded: boolean(not in published type)exitPolicymay be{}when unset — treat missing keys as "not set"- DELETE is soft and idempotent — returns
{"success": true}even for unknown IDs