Skip to content

API reference

Trade journal (MCP)

The read-only MCP server for your trade journal, its journal:read key, the nine tools, the three prompts, and the TypeScript client.

The trade journal server is a remote MCP (Model Context Protocol) server. It gives an AI assistant, or your own code, read-only access to one trader's journal: accounts, closed trades, fills, funding, daily equity, metrics, open positions, payouts and the account's limits. For the trader's view, see Trade journal.

The server

Addresshttps://mcp.perpsfund.com
TransportStreamable HTTP, stateless. Send each JSON-RPC message as a POST; the answer is JSON. There is no session and no server-to-client stream: a GET answers 405.
Sign-inAuthorization: Bearer <key> (or X-API-Key), on every request, initialize included.
BrowsersNo CORS headers, as on the rest of the API: a key belongs in a server or a desktop app.

It has no tool that places, changes, closes or cancels anything, and the reads behind the tools write nothing to your accounts.

The key

The server takes a key with the journal:read scope and no other. Create one on Analytics, under Connect your AI, or in Settings by choosing Journal under Access. Creating any key needs API access and a second factor: see Authentication.

  • A read or trade key is refused here with 403 insufficient_scope.
  • A journal key is refused on every route of the trading API with 403 insufficient_scope.
  • A live key (pk_live_) reads your real accounts. A sandbox key (pk_test_) reads the sandbox's paper account.

Everything else about a key is as on the trading API, because the same gate checks it: expiry, revocation, the IP allowlist, and the rate limits, 600 per minute for a key and 2,000 per minute across all your keys, counted together with your calls to the trading API. One HTTP request is one call. A refusal at the gate is an HTTP error in the API's envelope, { "error": "<code>", "message": "<sentence>" }, with the codes on Errors. Every response carries x-request-id and the X-RateLimit-* headers.

Tools

Every tool is read-only. Money is in US dollars; times are ISO 8601 in UTC. from and to are a UTC day (2026-10-01) or an ISO 8601 time; a day as to means the whole day. A paged tool takes limit (1 to 500, default 100) and cursor, and answers nextCursor: pass it back as cursor for the page after, until it is null.

ToolArgumentsAnswers
list_accountsnoneaccounts: the rows of GET /accounts.
get_tradesaccountId, from, to, limit, cursortrades, newest first, and nextCursor. A trade is the row of GET /trades plus holdSeconds. realizedPnL is gross and fee is the round-trip fee: net is one less the other.
get_fillsaccountId, symbol, from, to, limit, cursorfills, newest first, and nextCursor. A fill is the row of GET /fills plus accountId.
get_fundingaccountId, from, to, limit, cursorfunding, newest first, and nextCursor: id, accountId, symbol, payment (negative is a charge), rate, positionSize, hour, createdAt.
get_daily_equityaccountId, from, toequity, oldest first: accountId, date, equity and balance at the 00:00 UTC reset that opened the day. At most 1,000 days, the newest of the window.
get_metricsaccountId, from, tometrics: see below.
get_open_positionsaccountIdpositions: the rows of GET /positions.
get_payoutsaccountId (optional), from, topayouts, newest first: id, type, accountId, amount, status, network, txHash, requestedAt, paidAt.
get_rulesaccountIdThe body of GET /accounts/{accountId}: balance and equity, the loss floors and the distance to each, the profit target and the distance to it, drawdown percentages, profit split, phase and status.

A tool's answer is in the result's structuredContent, and the same JSON as text in content. A tool that refuses answers isError: true with one line of text, <code>: <sentence>:

CodeWhen
account_not_foundThe account id is not one of the key owner's.
bad_datefrom or to cannot be read, or from is after to.
bad_cursorThe cursor is not one this tool gave out.

Metrics

get_metrics computes over the closed trades of the window, with the definitions of the Analytics page: net is realized P&L less the fee, a trade at exactly zero net is flat (neither a win nor a loss), and the win rate is wins over all trades. It reads at most 5,000 trades, the newest; truncated is true when the window held more.

FieldMeaning
trades, wins, losses, flatCounts.
winRatePctWins over trades, as a percentage. null with no trades.
netPnL, grossPnL, fees, volumeNet, realized before fees, fees paid, and notional traded (both legs).
profitFactor, profitFactorKindGross profit over gross loss when profitFactorKind is ratio. With wins and no losses the kind is no_losses and the factor is null; with neither it is none.
expectancyNet per trade.
avgWin, avgLoss, rewardToRiskAverages of each side (null when that side is empty) and their ratio.
best, worstThe best and worst single trade, net.
avgLeverageWeighted by notional.
holdtimedTrades, and avgMinutes, avgWinMinutes, avgLossMinutes.
streakscurrent (positive is wins in a row, negative losses), longestWin, longestLoss.
bySidelong and short, each with trades and netPnL.
bySymbolsymbol, trades, wins, netPnL, best first.
byDaydate (UTC) and netPnL.
byWeekdayweekday, trades, netPnL, Sunday first.
byHourhourUtc (the hour the trade closed), trades, netPnL. Hours with no trade are left out.

Drawdown, return and the risk ratios are not here: they need the marked-to-market equity curve, which the Analytics page draws.

Payout states

status is one of pending, claimable, paid, rejected or expired. With an accountId, the answer is that account's payouts plus the profit payouts that drew from all the trader's funded accounts together, whose accountId is null.

A sandbox key

A sandbox journal key reads the one paper account, whose id is demo; accountId can be left out. list_accounts, get_trades, get_metrics, get_open_positions and get_rules answer from it (get_rules in the shape of GET /sandbox/account). The sandbox keeps no fills, funding, daily equity or payouts, so those four answer empty lists.

Prompts

PromptWhat it asks the assistant to do
weekly-reviewReview the last seven days against the seven before: result, win rate, profit factor, best and worst trades, where money was made and lost, and how close the account came to its limits.
worst-habitFind the single most costly pattern in the last thirty days, show the trades that prove it, and propose one rule.
pre-session-checklistState where the account stands before a session: room to the limits, open positions, the current streak, and a largest acceptable loss per trade.

Each takes an optional accountId.

Call it from code

A tool call is one JSON-RPC request:

curl https://mcp.perpsfund.com \  -H "Authorization: Bearer $PERPSFUND_JOURNAL_KEY" \  -H "Content-Type: application/json" \  -H "Accept: application/json, text/event-stream" \  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_metrics","arguments":{"accountId":"<id>","from":"2026-10-01"}}}'

The TypeScript SDK wraps the tools as methods:

import { PerpsFundJournal } from "@perpsfund/api";
const journal = new PerpsFundJournal({ apiKey: process.env.PERPSFUND_JOURNAL_KEY! });
const [account] = await journal.accounts();const week = await journal.metrics(account.id, { from: "2026-10-05", to: "2026-10-11" });console.log(week.netPnL, week.winRatePct, week.profitFactor);
for await (const trade of journal.allTrades(account.id, { from: "2026-10-01" })) {  console.log(trade.symbol, trade.realizedPnL - (trade.fee ?? 0), trade.holdSeconds);}

A refusal throws PerpsFundError, with the code and message above.

Connect an assistant

AssistantSetup
Claude DesktopIn claude_desktop_config.json, add a server whose command is npx with the arguments -y, mcp-remote, https://mcp.perpsfund.com, --header, Authorization: Bearer <key>. Restart Claude Desktop.
Claude.aiAdd a custom connector with the address above and the request header Authorization: Bearer <key>. The request headers field is still being rolled out by Anthropic and may not be on your account.
ChatGPTNot supported yet: its connectors sign in with OAuth and cannot send a key.

Connect your AI on Analytics writes the Claude Desktop block for you, with a new key in it.