# Endpoints and Errors

> Every WakeTrades API v1 endpoint, with its method and purpose, and the stable error codes an agent can rely on.

Base URL: `https://waketrades.com/api/v1/`. JSON in and out. Routes marked 🔒 need `Authorization: Bearer <token>` ([Sign In With a Wallet](https://waketrades.com/docs/api/authentication.md)). The full schema is in [`/openapi.json`](https://waketrades.com/openapi.json).

## Account and Setup

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/session/nonce` | Message to sign for sign-in |
| `POST` | `/session/verify` | Signed message → session token (creates the account the first time) |
| `POST` | `/session/revoke-all` | Signs out every session of the wallet, this one included |
| `DELETE` | `/agent-link` 🔒 | Signs out every AI agent session of the wallet (an agent can sign itself out); the website stays signed in. A person connects an agent from the website's Profile, not through the API ([Connect Your AI Agent](https://waketrades.com/docs/account/connect-ai-agent.md)) |
| `GET` | `/me` 🔒 | Account, copy status, open positions, balance, network — see [below](#get-me) |
| `GET` | `/preview?planned=` 🔒 | What each exposure level would do with your balance and first leader, and the [recommended level](#recommended-level) for that balance — see [below](#get-preview) |
| `PUT` | `/settings` 🔒 | Leaders, exposure level, language; may return a [`warning`](#warning-copies-too-small). During sign-up, `{ "level" }` alone is saved first (state stays `setup`, answer has `next: "GET /leaders/suggested"`); the next step needs at least one leader. `?dryRun=true` checks it all and answers without saving ([Dry Run](#dry-run)) |
| `PUT` | `/advanced` 🔒 | Settings of the Advanced level (every plan, free trial included; level `advanced`): size, exposure, alerts, markets, leverage, sides, stop per copy, daily loss limit — see [Advanced Settings](#advanced-settings) |
| `PUT` | `/max-positions` 🔒 | `{ "maxPositions": 5 }` or `null`: maximum of open copies, at level `advanced` only |
| `GET` | `/leaders/suggested?level=` 🔒 | Suggested trader for a level (default: your saved level) from our latest Monthly Report — see [Suggested Traders](#suggested-traders). `400 invalid_level`, `400 invalid_count`, `404 no_suggestion` before the first report |
| `PUT` | `/leaders/name` 🔒 | `{ "leader": "0x…", "name": "…" }`: a private name for one of your leaders, 24 characters at most (`""` resets it). `400 leader_not_followed`, `400 invalid_name` |
| `GET` | `/leaders/stats` 🔒 | Statistics of your leaders, from the cards already computed (no Hyperliquid read). A leader is `null` until their card exists: `GET /leader/{address}` computes it |
| `POST` | `/agent` 🔒 | Address of the trading key to authorize |
| `POST` | `/agent/approve` 🔒 | Relay your signed `approveAgent` to Hyperliquid |
| `POST` | `/agent/verify` 🔒 | Accept the Terms, confirm the key, start copying; may return a [`warning`](#warning-copies-too-small) |
| `POST` | `/account/unified` 🔒 | Body `{}`. Switch the Hyperliquid account to a Unified Account with the trading key, no wallet signature. Call it right after `/agent/verify` — see [Unified Account](#unified-account) |
| `GET` `PUT` `DELETE` | `/webhook` 🔒 | Alert channel for agents ([Webhooks](https://waketrades.com/docs/api/webhooks.md)) |
| `PUT` | `/notifications` 🔒 | `{ "muted": true }` stops position-move alerts |
| `POST` | `/telegram/link` 🔒 | `{ "name" }` (optional wallet name, 1 to 20 characters) → `{ url, oidcUrl, expiresInSec }`: a link to open the WakeTrades Telegram bot, valid 15 minutes; Telegram is an alert channel like the webhook. `400 invalid_wallet_name` |
| `POST` | `/telegram/unlink` 🔒 | Unlink Telegram → `{ state }`; copying continues. `409 telegram_not_linked` |
| `POST` | `/notices/dismiss` 🔒 | `{ "id" }` hides a notice of `status.notices` for good → `{ notices }`. `400 unknown_notice` |
| `POST` | `/report/collapse` 🔒 | `{ "edition": "YYYY-MM" }` → `{ reportCollapsed }`: folds the Monthly Report card until the next edition (used by the web app). `400 invalid_edition` |
| `PUT` | `/order-mode` 🔒 | `{ "mode": "market" }` or `"limit"`: how copies enter — see [Order Mode](#order-mode) |
| `GET` | `/feedback` 🔒 | Your last 30 messages to the team and the team's replies, oldest first: `{ canWrite, unread, messages: [{ from: "you" \| "team", text, at }] }` |
| `POST` | `/feedback` 🔒 | `{ "message": "…", "email": "…" }` (email optional): feedback, a bug report or an idea, to the team. Activated accounts only (`403 account_required` before); 1–2,000 characters; up to 10 per 24 hours (`429 feedback_limit`). Returns `201 { sent, emailed }` |
| `POST` | `/feedback/read` 🔒 | Mark the team's replies as read: `{ unread: 0 }` |

## Copying

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/positions` 🔒 | Copied positions with stop and take profit, and `openedAt` (ms) for their age. Prices and PnL may be up to 30 seconds old, longer during a Hyperliquid outage or heavy load |
| `GET` | `/orders?n=50&before=<ms>` 🔒 | Copy orders, including skipped trades and why; `n` 1 to 100, default 50 |
| `GET` | `/journal?n=20` 🔒 | Recent events (the same ones as the webhook) |
| `GET` | `/history.csv` 🔒 | Closed copies as CSV (after a first payment) |
| `POST` | `/pause` 🔒 | Stop opening new copies; open ones stay managed |
| `POST` | `/resume` 🔒 | Resume copying (needs Telegram or a webhook) |
| `POST` | `/close-position` 🔒 | `{ "coin": "ETH" }` closes one copy at market. If you close a copy yourself, the leader's later adds on that coin are not copied; their next new position is |
| `POST` | `/close-losing` 🔒 | Close at market the copied positions currently in loss (an action of the drawdown alert) |
| `POST` | `/close-all` 🔒 | Close every copy at market and pause |
| `POST` | `/emergency-stop` 🔒 | Emergency stop: the same as `/close-all`, with what it doesn't do (`keyRevoked: false`) — see [Security](https://waketrades.com/docs/api/index.md#security-and-terms-of-engagement) |
| `GET` | `/stream` 🔒 | WebSocket: every event and your leaders' moves live, commands back — see [Real-Time Stream](https://waketrades.com/docs/api/stream.md) |

## Plans

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/offer` | Public prices |
| `GET` | `/subscription` 🔒 | Receiver, offers, trial and plan dates, payments. `founder`: your founder price applies. Before activation, `founderOnActivation: true` while founder seats remain (the same rule as `/offer`): the offers then show the founder price the account gets when its trial starts. In `payments`, `tx_hash` is `null` until the hourly reconciliation with Hyperliquid's ledger fills it (within the hour) |
| `GET` | `/pay/prepare?plan=&chainId=` 🔒 | The payment to sign: `usdSend` from your free perps USDC, or `sendAsset` from your spot USDC ([Pay With USDC](https://waketrades.com/docs/api/payments.md)) |
| `POST` | `/pay` 🔒 | Relay your signed payment and apply the plan. `?dryRun=true` checks it all without relaying ([Dry Run](#dry-run)); accepts an [`Idempotency-Key`](#idempotency) |

## Monthly Report

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/report?level=&month=` 🔒 | Up to 5 traders per exposure level (`prudent`, `equilibre`, `risque`), picked by our filters then ranked on what a copy would have made over 90 days, no scalpers, with what copying each one would have earned and lost at that level over 90 days, their type, win rate, profit factor, trades per month, holding time and positions open at once (average and peak). The latest edition by default, or `month=YYYY-MM` |
| `GET` | `/report.md?level=&month=` 🔒 | The same report as Markdown (`text/markdown`), same access: to read it as an agent or paste it into an assistant |


`GET /report` answers `404 no_edition` if no edition is published yet, and `GET /leaders/suggested` answers `404 no_suggestion`: choose a trader yourself by address and check it with `GET /leader/{address}`.

With a paid plan in progress, `access` is `full`. Otherwise, during the free trial for example, it's `preview`: each entry keeps its rank, its type and what copying it would have earned and lost at that level (rounded to 1%), the trader's own return over 90 days and 6 months and win rate (`leader.return90d`, `leader.return6m`, `leader.winRate90d`, rounded to 1%) and profit factor (`leader.profitFactor90d`, one decimal), without name, address or the trader's other account figures, and has `locked: true`. Both views include `yourCopy`: for each level, the top 3 traders copied together at that level's size, by balance (1,000 to 25,000 USDC) — result, median, 1-in-10 and worst 30 days, worst drop, alerts crossed, peak exposure and margin (`yourCopy` is simulated: limit orders at the trader's price, then market after 1 min if unfilled, about 30 s after the trader, 0.026% average fee per side, funding not included). With `full`, each entry's `copy` also gives `trades` (the trader's trades in the 90 days), `copies`, `simShare` and `skipped` (why trades were not copied), and `leader.stressBtc10` (the trader's estimated move if BTC drops 10% in a day). Both views also include `yourResults`, the real figures of the signed-in wallet for the edition's month: `{ ready: false }` until a month of copy trading is closed, then `from`, `to`, `startEquity`, `endEquity`, `deposits`, `withdrawals`, `change` (without deposits and withdrawals), `realized` (closed copies, fees and funding included), `closed`, `unrealized` and `open` (copies still open at the end). Editions come out on the 1st of each month for the month just ended. On the test environment (Testnet USDC), `full` is reserved to the WakeTrades team.

## Public

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/hello` | Start here: what WakeTrades is, three quick-start steps with their endpoints, the Hyperliquid `network`, `copying` (`on`, `pause` or `halt`: the switch for every account), `links` (docs, OpenAPI, llms.txt, MCP, agent skills, Terms) and `version`. No session, no Hyperliquid read ([Hello](#hello)) |
| `GET` | `/levels?equity=` | The four exposure levels (`prudent`, `equilibre`, `risque`, `advanced`) and their rules: size, leaders, total exposure, drawdown alerts, plans, `unifiedAccount` (`required`, or `xyz_only` at `advanced`), `minPracticalBalanceUsd`. With `equity` (USDC) or a session token (your Hyperliquid balance): `equity` and the [recommended level](#recommended-level) (`recommendedLevel`, `belowAllMinimums`, `recommendationNote`); otherwise these are `null`. `400 invalid_equity` |
| `GET` | `/leader/{address}` | A Hyperliquid trader's card: equity, positions, statistics (`stats.type`: `scalp`, `day`, `swing` or `unknown`); `stats.d90`: the same figures over 90 days, with `pnlUsd` and `pnlPct` (perps only); `stats.pnl30Usd` / `pnl30Pct`: the 30-day gain on perps. Signed in, each open position (`live.open`) also has its age, and `activity.closed` lists the 5 latest closed positions, newest first (`coin`, `side`, `value`, `entryPx`, `exitPx` average, `pnl` after fees, `holdMs`, `closedAt`). Without a session, `activity` has only `lastTradeAt` when the trader's trades were read in the last 10 minutes, otherwise it is `null`. `statsBusy: true` with `retryAfter` (seconds): no live read was free to compute the card (5 per minute for the whole service); ask again after that delay |
| `GET` | `/leader/{address}/positions` 🔒 | The trader's open positions now, in detail: value, entry, mark and liquidation prices, PnL, and age — `openedAt` (ms, from their last 2,000 trades), or `openedBefore` when the position is older than those trades; the age comes once `GET /leader/{address}` has read the trader's trades (kept 10 minutes). Read it before choosing a trader: report figures are month-end. `notCopied: true` would mark a market WakeTrades never copies; every market listed here is one we copy, so it is `false`. It does not say whether that position is or will be copied: **positions a leader already holds when you start copying are never copied**, only the trades they open afterwards. `orders`: the trader's open orders, newest first — `kind` (`limit`, `sl` stop loss, `tp` take profit), `coin`, `side` (1 buy, −1 sell), `reduceOnly`, `wholePosition` (a stop or take profit on the full position), `value` (USDC), `px` (trigger price for `sl` / `tp`, else limit price), `tp` and `sl` (trigger prices of the take profit and stop loss attached to that order, or `null`), `placedAt`. Reads not already kept count toward 2 per minute per signed-in account (`429` beyond) |
| `GET` | `/teapot` | `418 I'm a teapot` with a haiku about copy trading. Just for fun |

## GET /me

- `client`: `address`, `state`, `lang`, `level`, `leaders`, `leader_names`, `wallet_name`, `agent_address`, `agent_approved_at`, `agent_valid_until`, `telegram_chat_id`, `telegram_username`, `trial_started_at`, `plan_kind`, `plan_expires_at`, `paused_reason`, `tos_accepted_at`, `tos_version`, `created_at`.
- `status`: the copier — `state`, `level`, `leaders`, `orderMode`, `accountMode` (Hyperliquid's mode: `default`, `unifiedAccount`, `portfolioMargin`…; `null` before activation), `notices`, `trial_ends_at`, `plan_expires_at`, `advanced` ([Advanced settings](#advanced-settings), defaults included; on the 6-month plan also at other levels, where they are kept but not applied; `null` otherwise), `advancedRulesActive` (how many Advanced settings differ from the defaults; `0` at other levels), `dailyLossUntil` (ms while a daily loss limit is in progress, otherwise `null`).
- `notices`: `["standard_account"]` while the account isn't a Unified Account (switch with `POST /account/unified`); `["unified_now"]` once an account seen as standard has become unified. `POST /notices/dismiss { "id" }` hides one for good.
- `equity`: the Hyperliquid balance, read at most every 10 minutes; read again right after a payment or a switch to a Unified Account.
- `unified`: `true` when `status.accountMode` is `unifiedAccount` or `portfolioMargin`; before activation, read from Hyperliquid; `null` if unknown.
- `copiesOnHold`: `{ "held": true, "reasons": ["not_unified", "manual_positions"], "coins": ["BTC"] }`. `held` is `true` when your leaders' next trades would not be copied now. Computed from what `/me` already reads (no extra Hyperliquid read): check it after setup and whenever copies don't appear.
- `renewal`: `{ "days": 7, "until": 1795000000000, "planKind": "1m", "founderPrice": true }` while a paid plan is close to its end (30 days for a 6-month plan, 14 for 3 months, 7 for 1 month), otherwise `null`. Renew with `GET /pay/prepare` then `POST /pay` before `until`: the new plan starts at `until`, and `founderPrice: true` means the founder price is kept. A `plan_expiring` [webhook](https://waketrades.com/docs/api/webhooks.md) event is sent at the start of this period and the day before.

| Reason | Why new copies are on hold | What to do |
| --- | --- | --- |
| `not_activated` | Sign-up not finished (`setup`, `awaiting_signature`, `awaiting_telegram`) | Finish [Set Up Copying](https://waketrades.com/docs/api/set-up-copying.md) and link a webhook |
| `paused` | Copying is paused (by you, or because the last alert channel was removed: see `client.paused_reason`) | `POST /resume` |
| `trial_expired`, `plan_expired` | The free trial or the plan has ended | Pay a plan ([Pay With USDC](https://waketrades.com/docs/api/payments.md)) |
| `key_invalid` | The trading key expired or was revoked | Sign a new key (Set Up Copying, steps 2 to 4) |
| `platform_paused` | WakeTrades paused new copies for everyone (incident) | Wait; open copies stay managed |
| `no_leaders` | No leader followed | `PUT /settings` with leaders |
| `no_margin` | No balance on the Hyperliquid account | Deposit USDC |
| `not_unified` | Prudent, Balanced, Risky: the account isn't a Unified Account | `POST /account/unified` with `{}` |
| `manual_positions` | Prudent, Balanced, Risky: positions opened outside WakeTrades, listed in `coins` | Close them on Hyperliquid |
| `xyz_not_unified` | Advanced on a standard account: only the xyz markets are held (`held` stays `false`) | `POST /account/unified` to copy xyz markets too |

## Account States

`client.state` (and `status.state`):

| State | Meaning |
| --- | --- |
| `setup` | Signed in; level or leaders not saved yet |
| `awaiting_signature` | Settings saved; trading key not signed yet |
| `awaiting_telegram` | Key confirmed; no alert channel yet (link a webhook or Telegram to start) |
| `trial` | Copying, 14-day free trial (`status.trial_ends_at`) |
| `active` | Copying, plan paid. **Paying during the trial switches the state to `active` at once, while `status.trial_ends_at` is still in the future: the plan starts when the trial ends** (`plan_expires_at` = end of the trial + the plan's length), so no trial day is lost |
| `paused_user`, `paused_auto` | Paused: no new copies, open ones stay managed |
| `trial_expired`, `expired` | Trial or plan ended without payment: no new copies, open ones stay managed |
| `key_invalid` | Trading key expired or revoked: nothing is sent until a new key is signed |

## Unified Account

At **Prudent, Balanced and Risky**, new copies open only on a Hyperliquid **Unified Account** with **no position opened outside WakeTrades**. Until then, each new trade of your leaders is skipped (`skip`: `not_unified`, or `manual_positions` for positions opened outside WakeTrades) and an `account_blocked` event is sent at most once a day. At **Advanced**, a standard account copies, except the **xyz** markets (stocks, indices, commodities), which always need a Unified Account (`hip3_unified`). `POST /account/unified` with `{}` switches the account with the trading key, without a wallet signature; it returns `{ "mode", "unified" }` and sends an `account_unified` event. On Hyperliquid, the USDC moves from Perps to Spot, where it backs every market.

## Recommended Level

An account needs a minimum balance to copy, because Hyperliquid has a minimum order size (10 USDC; WakeTrades uses 11 for rounding). Each leader is copied at their position × your balance ÷ theirs × the level's size ÷ 3; on a small balance, many copies come out under that minimum. Such a copy is **not skipped: it opens at 11 USDC**, larger than the level's share, so the account takes more risk than its level shows (it is skipped only when the coin's smallest size step would push it above 22 USDC, `too_small`, or when the exposure cap is reached, `cap`). Minimum practical balance: **1,100 USDC at Prudent, 550 at Balanced, 300 at Risky** (Advanced: 300 with Risky's size) — see [why](https://waketrades.com/docs/leaders/risk-levels.md#minimum-practical-balance).

`recommendedLevel` is the most prudent of `prudent` → `equilibre` → `risque` whose minimum your balance covers. Under every minimum, it is `risque` with `belowAllMinimums: true`: copies open at the 11 USDC floor whatever the level, and Risky, with the largest share per leader, stays closest to your leaders' proportions. `advanced` is never recommended. `recommendationNote` says why in one sentence. The recommendation is only advice: any level can be chosen. With a session token and USDC still in spot on a standard account (a new account's first transfer often lands there), `equity` is `0`: the answer then adds `spotUsdc` and `equitySource: "spot"`, and recommends on that spot balance. It backs copies once WakeTrades switches the account to unified (`POST /account/unified`, right after the trading key).

```json
{ "equity": 150, "recommendedLevel": "risque", "belowAllMinimums": true, "recommendationNote": "Your 150 USDC is under every level's minimum balance (…)" }
```

## GET /preview

What each level would do with your balance (or `?planned=` USDC if the account has no balance yet) and your first leader, plus `recommendedLevel`, `belowAllMinimums` and `recommendationNote` for that balance ([Recommended Level](#recommended-level)):

| Field | Meaning |
| --- | --- |
| `equity`, `walletEquity`, `planned` | Balance used; your Hyperliquid balance; `true` if `planned` was used |
| `leader`, `hasStats` | Your first leader; whether their card has statistics (otherwise most fields are `null`) |
| `minOrderUsd` | `11`: Hyperliquid's 10 USDC minimum order plus a rounding margin |
| `levels.<level>.nextCopyUsd` | Size in USDC of a copy of the leader's median trade at this level, at the trade's full size (the largest size the leader's position reached, adds included: copies follow adds). Never below 11: a proportional copy under the minimum is **opened at 11 USDC**, not skipped |
| `levels.<level>.tooSmallPct` | Share (%) of the leader's trades over 30 days whose copy, at the trade's full size, would stay under the minimum at your balance and level. They open at 11 USDC, larger than your level's share: `nextCopyUsd: 11` with `tooSmallPct: 100` means almost every copy opens at the floor. It falls as the level's size rises (Prudent ≥ Balanced ≥ Risky) |
| `levels.<level>.exposureX` | Estimated peak total exposure, in times your balance (3 leaders each at their largest position, capped by the level) |
| `levels.<level>.liquidationDistancePct` | Approximate adverse move (%) that would liquidate the account at `exposureX`, in cross margin |
| `levels.<level>.copiesPerMonth`, `scale` | The leader's trades per month; your copy ratio (your balance ÷ theirs × level size ÷ 3) |
| `levels.<level>.minPracticalBalanceUsd` | Balance from which a typical leader's median trade copies above the minimum at this level: 1,100 (Prudent), 550 (Balanced), 300 (Risky and Advanced by default). See [The Four Exposure Levels](https://waketrades.com/docs/leaders/risk-levels.md#minimum-practical-balance) |
| `riskAckAboveUsd` | Above this balance, Risky needs `riskAck: true` |

## Warning: Copies Too Small

`PUT /settings` and `POST /agent/verify` succeed as usual and may add, at Prudent, Balanced or Risky:

```json
{ "warning": { "code": "copies_too_small", "leader": "0x…", "tooSmallPct": 100, "equity": 65.5, "minOrderUsd": 11, "minPracticalBalanceUsd": 1100, "message": "…" } }
```

It means at least 90% of that leader's trades would copy under the minimum order at your balance: they open at 11 USDC, larger than your level's share, so the account takes more risk than its level shows. Nothing is blocked. Deposit more, pick a larger level, or another leader. It is computed from the same readings as `/preview` and the leader's card already computed; without a card, no warning.

## Suggested Traders

`GET /leaders/suggested?level=risque` proposes a trader from our latest [Monthly Report](https://waketrades.com/docs/leaders/monthly-report.md) for the level (`advanced` uses `risque`'s list), with the report's 90-day figures. The rule, the same on the site, the API and MCP:

1. The suggestion of each more prudent level is set aside first, in order: Prudent's, then Balanced's.
2. Among the level's #3 to #5 not set aside, the one who opened the most positions in the 30 days before the report is suggested (a tie goes to the better rank). Never #1 or #2. If #3 to #5 were all set aside, the most active of them is suggested anyway. A list shorter than 3, or an edition without this activity figure: the level's top 5 is read from #5 (or the last) up to #1 and the first trader not set aside is suggested.
3. **One suggestion per level, for every account**: three traders of the report in all. You can follow up to 3 leaders (5 at Advanced). The full lists are in the [Monthly Report](https://waketrades.com/docs/leaders/monthly-report.md) with a paid plan. `count` above 1 is ignored (the answer adds `"countLimited": true`). Risky strongly recommends 2 leaders: choose the second one yourself.

```json
{ "level": "risque", "list": "risque", "rank": 3, "address": "0x…", "month": "2026-10",
  "stats": { "winRatePct": 58, "profitFactor": 1.6, "tradesPerMonth": 24, "medianHoldHours": 9.5 }, "note": "…",
  "liveCheck": "GET /api/v1/leader/0x…/positions",
  "suggestions": [ { "rank": 3, "address": "0x…", "liveCheck": "…", "…": "…" } ] }
```

The top-level fields are the first suggestion (as before `count` existed); `suggestions` lists them all, each with the same fields. The report's figures are month-end: **before choosing, read each trader's live positions** with `liveCheck` (`GET /leader/{address}/positions`), since a trader can hold a large losing position now. The rest of the list comes with a plan.

## Order Mode

`status.orderMode` says how copies enter; `PUT /order-mode` changes it (MCP `set_order_mode`).

- `limit` (default for new accounts): a post-only order at the leader's entry price (or the best price on the book if better). Unfilled after 1 minute, it is cancelled and the copy opens at market, unless the price is more than 1% from the leader's entry. If the leader exits before it fills, nothing is copied and a `limit_missed` event is sent.
- `market`: the copy opens at market right away; skipped (`late`) if the price is already more than 1% from the leader's entry.

```json
{ "mode": "market" }
```

Orders already waiting keep their mode.

## Advanced Settings

At level `advanced` (every plan, free trial included), `PUT /advanced` changes the settings (MCP `set_advanced`, snake_case names). Fields you leave out keep their value. Every filter and limit below is **off by default**: until you set one, copying works as before. Size, exposure, alerts, automatic stop, adds and TP/SL: see [Set Up Copying](https://waketrades.com/docs/api/set-up-copying.md#advanced-settings).

| Field | Values | Default | What it does |
| --- | --- | --- | --- |
| `markets` | list of `crypto`, `btc`, `xyz`, `gold`, `wti`, `indexes`, no duplicates | `[]` (all markets) | Markets new copies may open on. `crypto`: any market of Hyperliquid's main perp dex (not xyz); `btc`: `BTC` only; `xyz`: any market of the xyz dex (tokenized stocks, indices, commodities); `gold`: `xyz:GOLD`; `wti`: `xyz:CL` only (not Brent); `indexes`: `xyz:SP500`, `xyz:XYZ100`, `xyz:JP225`, `xyz:KR200` |
| `minMarketLeverage` | integer 1 to 50, or `null` | `null` | Copy only markets whose maximum leverage on Hyperliquid is at least this |
| `maxLeverage` | integer 1 to 50, or `null` | `null` | Cap on the leverage set on your copies (otherwise the market's maximum, in cross margin). It changes the margin used, not the size of the copy |
| `sides` | `both`, `long`, `short` | `both` | Copy only new positions on this side |
| `copyStopPct` | number 1 to 90, or `null` | `null` | Your own stop on each new copy: reduce-only, at the entry price −X % (long) or +X % (short) |
| `dailyLossPct` | number 1 to 50, or `null` | `null` | When your balance, deposits and withdrawals excluded, falls X % below its first reading of the UTC day, no new copies for 24 hours |
| `tp1Pct`, `tp1ClosePct` | number 1 to 1000 or `null`; 1 to 100 | `null`; `50` | Take profit 1 on each new copy, at a PnL of +X % of the copy (leverage included, as Hyperliquid shows it: at 10×, 20 = +2 % price); closes Y % of the copy |
| `tp2Pct`, `tp2ClosePct` | number above `tp1Pct` or `null`; 1 to 100 | `null`; `100` | Take profit 2; closes Y % of what is left |
| `slPct`, `slClosePct` | number 1 to 100 or `null`; 1 to 100 | `null`; `100` | Your own stop loss on each new copy, at a PnL of −X % of the copy; closes Y % of the copy (100 = all) |
| `breakEvenAfterTp1` | boolean | `false` | After take profit 1, move the copy's stop to break-even (entry price plus 0.1 % of fees) |

```json
{ "markets": ["btc", "gold"], "minMarketLeverage": 20, "maxLeverage": 5, "sides": "long", "copyStopPct": 8, "dailyLossPct": 10 }
```

- **Which trades open.** The ticked `markets` combine with OR (`["btc", "gold"]` copies BTC and gold); `minMarketLeverage` and `sides` apply on top (AND). These rules decide only which **new** copies open: adds, reductions and closes of a copy already open are always followed, even if you change the rules afterwards.
- **Skipped trades.** A leader's trade not copied by these rules shows in `GET /orders` (and on the [stream](https://waketrades.com/docs/api/stream.md)) as a skip with a stable code: `market_filtered`, `leverage_filtered`, `side_filtered` or `daily_loss_limit`.
- **`maxLeverage`** is set when the copy opens and kept for its adds.
- **`copyStopPct`.** The stop stays at that price, computed from the copy's first entry, and is resized to the whole copy after adds and reductions. If the leader's copied stop (`copyLeaderTpSl`) is closer to the entry, the leader's is kept instead. It is capped at 80 % of the distance to liquidation; under 1 %, the trade is skipped (`liq_close`). If no stop can be placed, the copy is closed right away. Copies already open keep the stop they opened with.
- **`dailyLossPct`.** The day's reference is your balance's first reading of the UTC day; a plan payment is taken out of it (it is not a loss). Deposits and withdrawals since that reading do not count: before the limit applies, WakeTrades reads your Hyperliquid ledger and takes money that came in out of your balance and adds money that left back to it (moves between your perps and spot balances are ignored). If the ledger cannot be read, the limit is not applied on that check and is checked again at the next one, 5 minutes later. When the limit is reached, a `daily_loss_limit` event goes to your [webhook](https://waketrades.com/docs/api/webhooks.md) and Telegram, and `status.dailyLossUntil` gives the end of the 24 hours. Copies already open stay managed: adds, reductions, closes, stops. Setting `dailyLossPct` to `null` lifts a limit in progress.
- **Your own take profits and stop loss** (`tp1Pct`, `tp2Pct`, `slPct`). Placed on Hyperliquid when the copy opens, as reduce-only trigger orders at the price that gives that PnL % for the copy's leverage; they sit next to your leader's copied stop-loss and take-profit (`copyLeaderTpSl`) and `copyStopPct`: the first one reached runs, and when several stops close the whole copy, the closest is kept. After a partial close the copy keeps following the leader on what is left (its adds, reductions and close), without buying the closed part back. If the part to close, or what would be left, is under Hyperliquid's minimum order, the level closes the whole copy. They apply to copies opened after you save them.
- **Kept across levels.** Your settings stay saved when you switch to another level and apply again when you come back to `advanced`. `GET /me` returns them in `status.advanced` at every level; they apply only at `advanced`, so `status.advancedRulesActive` (how many differ from the defaults) is `0` at other levels. `PUT /advanced` answers `{ "advanced", "advancedRulesActive" }`. `GET /levels` lists the bounds (`levels.advanced.fields`) and the market groups (`marketGroups`).

## Account Deletion

`GET /account/delete-check` 🔒, then `POST /account/delete/nonce` 🔒 and `POST /account/delete` 🔒 with a signed message, like sign-in. Allowed when every open copy has a stop (`stops_required` otherwise).

## Hello

`GET /api/v1/hello` (MCP: `hello`) is the first call to make. Public and cheap: no session, no Hyperliquid read.

```json
{
  "name": "WakeTrades",
  "what": "Non-custodial copy trading on Hyperliquid: we copy the traders you choose onto your own Hyperliquid account, and funds never leave it.",
  "quickStart": [
    { "step": "Sign in with your wallet (EIP-191)", "endpoint": "POST /api/v1/session/nonce, then POST /api/v1/session/verify" },
    { "step": "Choose an exposure level, then traders", "endpoint": "GET /api/v1/levels, PUT /api/v1/settings, GET /api/v1/leaders/suggested" },
    { "step": "Sign the trading key (it can trade, never withdraw)", "endpoint": "POST /api/v1/agent, POST /api/v1/agent/approve, POST /api/v1/agent/verify" }
  ],
  "network": "Mainnet",
  "copying": "on",
  "links": { "docs": "…/docs/api/", "openapi": "…/openapi.json", "llms": "…/llms.txt", "mcp": "…/mcp", "skills": "https://github.com/waketrades/hyperliquid-copy-trading-agent-skills", "terms": "…/terms" },
  "version": "0.1.0"
}
```

`copying` is the switch for every account: `pause` means no new copies for anyone (open ones stay managed), `halt` means no order at all during an incident. The full flow, webhook and payment included, is in [WakeTrades for AI Agents](https://waketrades.com/docs/api/index.md#the-whole-flow).

## Dry Run

Add `?dryRun=true` (or `"dryRun": true` in the body) to rehearse a call with no effect:

- **`PUT /settings`**: every check of the real call runs, with the same errors (`unknown_leader`, `too_many_leaders`, `risk_ack_required`…). The answer is what the call would give: `{ "dryRun": true, "client": { … as it would be }, "activeLeaders", "warning"?, "equity", "recommendedLevel", "belowAllMinimums", "recommendationNote" }`. Nothing is saved and no event is sent.
- **`POST /pay`**: every check of the real call runs (action, plan and amount, receiver, token, signer, age), plus the balance of the signed source (perps for `usdSend`, spot for `sendAsset`: `409 insufficient_usdc`). The answer is `{ "dryRun": true, "ok": true, "plan", "amount", "from", "source", "balanceChecked", "wouldRelay": true }`, or the same error the real call would give. Nothing is relayed to Hyperliquid, no plan is applied and nothing is written. Then send the same action without `dryRun`, within its 10 minutes: the real call runs every check again.

MCP: `dry_run: true` on `set_copy_settings` and `submit_payment`.

## Idempotency

`POST /pay`, `/agent/approve`, `/close-all`, `/close-position`, `/emergency-stop`, `/pause`, `/resume` and `/account/unified` accept an optional `Idempotency-Key` header: 1 to 255 printable ASCII characters, a UUID for example. Use a new key for each new action, and the same key to retry it.

- **First call**: runs normally; its answer is kept 24 hours for your account and that key.
- **Same key, same request** (same method, path and body) within 24 hours: the first answer comes back with the header `Idempotent-Replayed: true`, and nothing runs again. A payment is never relayed twice.
- **Same key, different request**: `422 idempotency_key_reused`.
- **Same key while the first call is still running**: `409 idempotency_in_progress`; retry a few seconds later.
- **Not kept**: `5xx` and `429` answers (retry with the same key, it runs again), and dry runs (`dryRun` ignores the key).
- **Errors are kept too**: a `4xx` answer comes back on every retry with the same key. Once you fix the cause (a new signature, more USDC…), send the corrected request with a **new** key.
- Keys are per account: another wallet can use the same key.

MCP: `idempotency_key` on `submit_payment`, `submit_trading_key`, `switch_to_unified_account`, `pause_copying`, `resume_copying`, `close_position`, `close_all` and `emergency_stop`.

## Errors

Every error is `{ "error": "<code>", "message": "<English text>", "fix": "<what to do next>", "docs": "<URL>" }`, sometimes with `detail` and other fields. `fix` is one short sentence saying what to do next; `docs` links to the most useful section of these pages. Codes are stable; messages and fixes may change. MCP tool errors carry the same fields.

```json
{ "error": "stale_signature", "message": "The signed action is older than 10 minutes. Sign a new one.", "fix": "Prepare and sign a new payment, then send it within 10 minutes.", "docs": "https://waketrades.com/docs/api/payments#errors" }
```

| Status | Codes |
| --- | --- |
| `400` | `bad_request` (see `detail`), `invalid_idempotency_key`, `bad_input`, `invalid_address`, `invalid_url`, `https_required`, `public_host_required`, `port_not_allowed`, `unknown_leader`, `too_many_leaders`, `advanced_only`, `invalid_level`, `invalid_count`, `invalid_equity`, `invalid_approval`, `terms_acceptance_required`, `no_agent_key`, `unknown_plan`, `invalid_payment_action`, `wrong_amount`, `wrong_destination`, `wrong_token`, `stale_signature`, `invalid_signature`, `wrong_signer`, `payments_closed`, `invalid_message`, `invalid_email`, `invalid_name`, `leader_not_followed`, `invalid_wallet_name`, `unknown_notice`, `invalid_edition`, `unknown_level` |
| `401` | `session_required`, `unknown_account`, `invalid_nonce`, `invalid_message`, `wrong_signer`, `agent_code_expired` |
| `403` | `banned`, `origin_not_allowed`, `account_required`, `wallet_required`, `paid_plan_required`, `view_only`, `access_required` |
| `404` | `not_found`, `no_suggestion`, `no_edition` |
| `409` | `idempotency_in_progress`, `risk_ack_required`, `agent_not_registered_yet`, `activation_required`, `alert_channel_required`, `webhook_not_linked`, `telegram_not_linked`, `stops_required`, `insufficient_usdc`, `key_required` |
| `422` | `webhook_unreachable`, `idempotency_key_reused` |
| `426` | `upgrade_required` (`GET /stream` without `Upgrade: websocket`) |
| `429` | `rate_limited`, `signup_limit`, `feedback_limit`, `too_many_streams` (3 open streams per account) |
| `451` | `geo_blocked` (restricted location of the request's IP address, when the location filter is on; it is off today) |
| `502` | `hyperliquid_refused` (`reason`: `no_deposit`, `too_many_agents`, `other`), `switch_failed` (Hyperliquid didn't switch the account: switch it in your Hyperliquid settings) |
| `503` | `idempotency_unavailable` (retry later, or without the header), `maintenance` (copies keep running; retry later), `platform_halt` (incident: no order is sent, close-all, emergency stop and account deletion included; your positions stay on Hyperliquid with their stops) |
