Get started

Authentication

Two ways in: sign in with your browser from an agent, or put a key in your code.

Two ways in, one account: sign in with your browser, or put a key in your code. Both end up in the same header and count against the same limits.

By browser

For anything that can open a browser window: Claude, ChatGPT, Claude Code, Codex and Cursor. There is no key to copy. The first time the host calls TradingPal it gets a 401 with a pointer, opens your browser on a TradingPal page, you sign in and approve, and the host keeps a token it refreshes by itself.

Where you sign in

  • ChatGPT and Claude: when you create the plugin or connect the connector (Quickstart, step 3).
  • Claude Code: type /mcp, pick tradingpal and choose sign in.
  • Codex: run codex mcp login tradingpal.
  • Cursor: click Needs login next to the server.

Where it shows up

The approval appears on API Keys as a connected app, named after the host. Revoking it signs that host out; otherwise its tokens expire on their own, seven days for access and ninety for refresh. The MCP page has the flow step by step for anyone writing a host.

By code

For a script, a notebook or a server, anything that cannot open a browser: a key.

Create a key

Create one on API Keys, one per thing that will use it. The full key is shown once, at creation; after that the page keeps its prefix, name, creation time and last use. Put it in a .env file your code reads, kept out of version control:

.env
TRADINGPAL_API_KEY=tp_live_…

Send it in the header

Header
Authorization: Bearer tp_live_…

Keys start with tp_live_; the tokens a browser sign-in produces start with tp_oat_. The REST API and the MCP server accept either in the same header.

set -a; source .env; set +a
curl "https://api.tradingpal.io/api/v1/me" \
  -H "Authorization: Bearer $TRADINGPAL_API_KEY"

Key lifecycle

  • Shown once. The server stores a hash; the full key never appears again.
  • Revoke any time. Revoking is immediate; anything still using the key gets invalid_api_key.
  • Rotate by creating, switching, revoking. Create the new key, move it into place, then revoke the old one. Nothing is lost in between.
  • Keep it out of transcripts. An agent host reads a key from its configuration or the environment, never from the conversation. If you paste a prompt that includes a key, treat that conversation as private and rotate the key when you are done.

Either way

The plan

API and MCP access requires Premium+ subscription at $30/month; it includes all of the benefits in Premium plus API and MCP access, and Premium alone does not unlock them. A sign-in or a key on an account without the plan gets forbidden until the account has it; the key itself stays valid.

Which routes need it

RoutesSign-in or key
/me, /scan, /tickers, /tickers/{symbol}/patterns, /tickers/{symbol}/history, /tickers/{symbol}/chart.pngRequired. These read the scan and the setups on any ticker, or read or change the account's tracked tickers.
/families, /families/{family}/stats, /universeOptional. Keyless calls are limited per IP; keyed calls count against the key.
/demo, /demo/{symbol}, /demo/{symbol}/chart.pngNone. A fixed set of large caps, so an agent can show the data before anyone signs up.
MCP serverSigns the user in, or takes the key header. https://api.tradingpal.io/mcp/demo needs neither: demo and get_family_track_record work there, the other tools return an error that says how to connect.

Name your client

Optionally send X-TradingPal-Client: <your agent or script name>. Every request is logged with it, so when you ask about usage we can tell your script's calls from your agent's. MCP hosts are recorded by the client name they send at initialize.

Errors

StatusCodeWhen
401missing_api_keyThe header was not sent on a route that needs it.
401invalid_api_keyUnknown or revoked key.
401invalid_tokenA sign-in token that expired or was revoked. The host refreshes it or signs in again; the WWW-Authenticate header says which.
403forbiddenThe account is not on Premium+.
429rate_limitedOver the per-key or per-IP limit; details.retry_after_seconds says how long to wait.

Every error body has the same shape, and its fix field says what to do next.