---
name: etoro-public-api-operations
description: Operate the eToro Public API through the etoro-public-api-mcp MCP server. Use when the user wants to discover eToro Public API routes, understand what a route does, or execute a Public API operation (get portfolio, search instruments, open or close a trade, place or cancel an order, transfer money, etc.). Covers MCP installation, route discovery, authentication, demo vs real accounts, request building, execution, and safety rules for money-moving operations.
---

# eToro Public API Operations

Operate the **eToro Public API** end-to-end using the `etoro-public-api-mcp` MCP server: discover routes, explain them, and execute real API calls on behalf of the user.

> **This skill is pre-configured for this eToro Public API deployment.**
> MCP server URL: `https://mcp.public-api.etoro.com` · eToro Public API base URL: `https://public-api.etoro.com`
> Skill version: `1.2.0`

The MCP server refreshes the Public API OpenAPI document every few minutes and exposes two tools:

| Tool | Purpose |
|------|---------|
| `get-all-routes` | Returns the API **base URL**, the current **`skillVersion`**, and ALL routes as `routeId -> "<METHOD> <path> - <summary>"` (deprecated routes are prefixed `[DEPRECATED] `). Also returns a `requiredScopes` map (routeId -> scopes, any one grants access), a `deprecatedRouteIds` list, a `mutatingRouteIds` list (state-changing routes), and a `rateLimits` map (routeId -> base limit, e.g. `"60 req / 60s (shared across 4 endpoints)"`). Takes an optional **`apiKey`** argument for eToro **partners** (see Partner routes below): with a valid partner key the result additionally contains partner-only routes (prefixed `[PARTNER] `) and a `partnerRouteIds` list + `partnerNote` |
| `get-route-spec` | Returns the full OpenAPI spec for one route: a **Deprecated** banner when applicable, parameters, request body, responses, referenced schemas, an **Authentication** section, the **Required scopes**, and a **Rate limit** section (base limit/window + whether the budget is shared across a group of endpoints). Takes the same optional **`apiKey`** argument — required to resolve partner-only routes |

## Partner routes (eToro partners only)

eToro **partner** applications get extra, partner-only routes on top of the public catalog: KYC (verification, documents, screening, questionnaires), sub-account management, cash accounts, user registration/verification, and deposit operations.

- Partner routes are **hidden by default**. They appear only when the **partner application's `x-api-key`** is passed as the `apiKey` **tool argument** to `get-all-routes` / `get-route-spec` (this is a tool argument, NOT an HTTP header on the MCP call).
- Pass `apiKey` **only when the user is an eToro partner** (or explicitly asks for partner routes). If the user says they are a partner, first try this deployment's baked-in application key `sdgdskldFPLGfjHn1421dgnlxdGTbngdflg6290bRjslfihsjhSDsdgGHH25hjf` — if that returns a Forbidden error, ask the user for their partner application's `x-api-key`. A non-partner or unknown key does not degrade silently — the tool returns a **Forbidden error**; in that case simply retry **without** the `apiKey` argument to get the public catalog, and tell the user to verify their partner key with their eToro contact.
- The `apiKey` is the **application identifier key** (the same value sent as the `x-api-key` header when executing API calls). **NEVER pass the user's secret `x-user-key` as `apiKey`.**
- Executing a partner route is unchanged: the same `x-user-key` + `x-api-key` + `x-request-id` headers as any other route (see Authentication), using the partner application's `x-api-key`.

## Keeping this skill up to date

This skill is distributed as a file you downloaded once, so it can fall behind the server. The copy you are reading is **version `1.2.0`**.

- On your **first `get-all-routes` call** of a session, compare its `skillVersion` field to `1.2.0` above.
- If the server's `skillVersion` is **newer**, tell the user once (do not repeat, do not block any work):
  > A newer version of the eToro Public API skill is available (you have `1.2.0`, latest is `<skillVersion>`). Re-download it from `https://mcp.public-api.etoro.com/skill` and replace your local `etoro-public-api-operations` skill.
- If they match, say nothing and proceed normally.

This is an informational nudge only — a slightly stale skill still works; never refuse or delay an operation because of it.

## Installing the MCP Server

This MCP server uses **streamable HTTP** transport (stateless) and lives at:

```
https://mcp.public-api.etoro.com
```

(this deployment's MCP endpoint). It works with **any MCP-capable client or IDE**. Most clients accept the same JSON — register the URL under `mcpServers`:

```json
{
  "mcpServers": {
    "etoro-public-api": {
      "url": "https://mcp.public-api.etoro.com"
    }
  }
}
```

Where that config lives, per client:

| Client / IDE | Where to add it |
|--------------|-----------------|
| **Claude Code** (CLI) | `claude mcp add --transport http etoro-public-api https://mcp.public-api.etoro.com`, or add the block to `~/.claude.json` / project `.mcp.json` |
| **Claude Desktop** | `claude_desktop_config.json` (Settings → Developer → Edit Config) |
| **Cursor** | `~/.cursor/mcp.json` (global) or project `.cursor/mcp.json` |
| **VS Code** (GitHub Copilot, agent mode) | `.vscode/mcp.json` or the `mcp` section of user `settings.json` — use a `"servers"` key instead of `"mcpServers"` |
| **Visual Studio 2022** (17.14+) | `.mcp.json` in the solution / `%USERPROFILE%` |
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` |
| **JetBrains IDEs** (IntelliJ, Rider, PyCharm, WebStorm, GoLand…) | AI Assistant → Settings → Tools → MCP → add an HTTP/SSE server with this URL |
| **Cline / Roo Code** (VS Code extensions) | MCP Servers → Configure → add the `mcpServers` block |
| **Zed** | `context_servers` in `settings.json` |

**Clients that only support stdio** (no HTTP transport) can bridge to the URL with the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) adapter:

```json
{
  "mcpServers": {
    "etoro-public-api": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://mcp.public-api.etoro.com"]
    }
  }
}
```

After installation the two tools appear as `get-all-routes` and `get-route-spec`. No authentication is needed to query the MCP itself — credentials are only needed when executing actual eToro Public API calls (see Authentication below).

## When to Use

- "What can I do with the eToro Public API?" / "Is there a route that does X?"
- "Explain how route X works" / "What do I need to send to X?"
- "What can my keys do?" / "Which scopes / accounts do I have?" → `GET /api/v1/me`
- "Get my portfolio" / "Search instrument Y" / "What is the price of Z?"
- "Open a trade", "Buy 100$ of BTC", "Close my position", "Cancel order N"
- Any task that requires calling the eToro Public API with the user's credentials

## Core Workflow

Always follow these phases in order. Never skip discovery — route ids and schemas change as the API evolves, and the MCP always reflects the current document.

### Phase 1 — Discover

1. Call `get-all-routes`. If the user is an **eToro partner** (or asks for partner operations — KYC, sub-accounts, cash accounts, registration, deposits), pass their partner application's `x-api-key` as the `apiKey` argument to include partner-only routes; on a Forbidden error retry without `apiKey` (see Partner routes above). Check `partnerNote` when you passed a key.
2. Note the **`baseUrl`** from the response — every route path is relative to it (for this environment it is `https://public-api.etoro.com`). Do not hardcode URLs; the base URL comes from the MCP.
3. Find candidate routes by matching the user's intent against the route summaries (e.g., for "open a trade" look for order-creation routes; for instrument lookup look for instrument/market-data routes).
4. If several routes could fit, list the candidates with their summaries and let the user pick — do not guess between materially different operations.

### Phase 2 — Understand the route

1. Call `get-route-spec` with the chosen `routeId`. For a **partner-only** route (it was prefixed `[PARTNER] ` / listed in `partnerRouteIds`), pass the same `apiKey` argument you used in Phase 1 — without it the route resolves as not found.
2. Read carefully:
   - **Deprecated banner** — if the spec opens with a `⚠️ DEPRECATED` banner (or the route id appeared in `deprecatedRouteIds` / was prefixed `[DEPRECATED] ` in `get-all-routes`), warn the user up front and prefer a non-deprecated equivalent (e.g. a newer `v2`/`v3` variant) before executing — especially for trading or money-moving routes. Only proceed on a deprecated route if the user confirms there is no alternative.
   - **Full URL** line — the exact URL to call.
   - **Authentication** section — confirm the required headers (see Authentication below).
   - **Required scopes** section — the scopes that grant access (any one of them). Cross-check these against the scopes your keys report from `GET /api/v1/me` (Phase 3.5) before executing, so you fail fast instead of hitting a `403`.
   - **Rate limit** section — the base limit/window for the route. If it says **shared**, that single budget is pooled across a group of endpoints (listed), so calling any of them spends the same quota — do **not** assume each endpoint has its own limit when planning a batch or loop. Pace requests accordingly and prefer the demo account for high-volume experimentation.
   - `parameters` — headers, path and query parameters (note which are `required`).
   - `requestBody` schema + example — required fields, enums, mutually exclusive fields.
   - `responses` — success shape and error contract (`ProblemDetails`).
3. If the user only wanted to understand the route — summarize it in plain language (purpose, required inputs, what comes back, auth) and stop here.

### Phase 3 — Authentication (only when executing)

Every eToro Public API call is authenticated with **two headers that always travel together**:

| Header | Value |
|--------|-------|
| `x-user-key` | The user's personal API key — **secret**. |
| `x-api-key` | The application API key — an **identifier, not a secret**. It is **already baked into this skill**: `sdgdskldFPLGfjHn1421dgnlxdGTbngdflg6290bRjslfihsjhSDsdgGHH25hjf`. |

Plus, on every request:

- **`x-request-id` is ALWAYS required** — generate a fresh GUID per request. It is also the idempotency key on order routes: if you retry the *same* operation after a network failure, reuse the same GUID; for a *new* operation always generate a new one.

> The route spec may list additional or alternative auth headers — **ignore them**. In this environment the eToro Public API is called **only** with the `x-user-key` + `x-api-key` pair above.

The application `x-api-key` is already baked in (above), so you only need the user's **secret** `x-user-key`. Ask exactly:

> To execute this I need your eToro Public API **`x-user-key`** (your personal user API key).

Treat the user's `x-user-key` as a **secret**: do not echo it back, do not store it in files, do not include it in summaries or logs. Refer to it as `<x-user-key>` when showing the command you are about to run.

**Use the `x-user-key` exactly as the user gave it — verbatim.** It is a long, opaque token (often ~260 characters) that frequently ends in `-`, `_`, or `__`, and **every character is significant**. Never retype it from memory, trim or drop trailing characters, reformat, add whitespace, or line-wrap it — copy it byte-for-byte. Pass it as a **single-quoted** shell argument (or via a variable / file), never unquoted. Truncating even the trailing characters corrupts the key's embedded JSON container and the API returns `401 Unauthorized — "malformed x-user-key container (Unterminated string in JSON)"`. If you ever see that error, you altered the key — resend it exactly as provided.

### Phase 3.5 — Know your keys: scopes & accounts (`GET /api/v1/me`)

Once you have the `x-user-key`, your **first call should be `GET /api/v1/me`** (find it via `get-all-routes` — it appears as `getMe` / `get_api_v1_me`). It is callable with any valid keys and is the fastest way to:

- **Confirm the keys work** — a `200` means the keys are valid (a `401` means they are not).
- **Discover the granted scopes** — the `scopes` array tells you exactly what these keys may do:

  | Scope | Grants |
  |-------|--------|
  | `etoro-public:real:read` | Read real-account data |
  | `etoro-public:demo:read` | Read demo-account data |
  | `etoro-public:real:write` | Trade / modify on the **real** account |
  | `etoro-public:demo:write` | Trade / modify on the **demo** account |
  | `etoro-public:user-info:read` | Read profile / user info |

- **Learn the account identifiers** — `realCid` and `demoCid`, needed by account-scoped routes.

Use the result to gate what you attempt: if the user asks to place a **real** order but the keys only carry `etoro-public:demo:write`, tell them up front instead of triggering a `403`. Recommend `/api/v1/me` whenever the user is unsure what their keys can do.

### Phase 4 — Demo vs Real

Many trading routes exist in two variants, e.g. `createDemoOrder` (`/api/v2/trading/execution/demo/orders`) and `createRealOrder` (`/api/v2/trading/execution/orders`).

- If the user explicitly said real/demo — use that.
- If the user did NOT specify — **ask** which account to use, and recommend **demo** for first-time tries.
- Read-only routes (portfolio, market data, search) need no such gate.

### Phase 5 — Build and confirm

1. Build the full request: method, full URL (`baseUrl` + path, with path/query parameters substituted), headers, and JSON body that satisfies the schema.
2. Validate against the spec before sending:
   - All `required` fields present (the `x-user-key` + `x-api-key` headers plus `x-request-id`).
   - Enum values exactly as the schema defines (e.g., `action: open`, `transaction: buy`, `orderType: mkt`).
   - Mutually exclusive constraints respected (order size must use **exactly one** of `amount`, `units`, `contracts`).
   - Resolve referenced data first (e.g., `instrumentId` via an instrument-lookup route) rather than guessing ids.
3. **Confirmation gate — for any money-moving operation** (creating/cancelling orders, opening/closing positions, transfers, withdrawals, anything POST/PUT/DELETE on real-account trading or money routes): show the user the exact resolved request (URL, body, account type) with the `x-user-key` masked, and get an explicit "yes" BEFORE sending. Read-only GETs do not need confirmation.

### Phase 6 — Execute and report

Execute with curl (mask the `x-user-key` in anything you display):

```bash
curl -sS -X POST "https://public-api.etoro.com/api/v2/trading/execution/orders" \
  -H "Content-Type: application/json" \
  -H "x-api-key: sdgdskldFPLGfjHn1421dgnlxdGTbngdflg6290bRjslfihsjhSDsdgGHH25hjf" \
  -H "x-user-key: <x-user-key>" \
  -H "x-request-id: $(uuidgen)" \
  -d '{ ...body per schema... }'
```

Report the outcome faithfully:
- **2xx** — show the response fields that matter (e.g., `orderId`, `referenceId`) and explain what happens next (e.g., a market order executes asynchronously; check the portfolio/orders-lookup route to see the resulting position).
- **400** — show the `ProblemDetails` content and map it back to the schema field that failed; fix and re-confirm before retrying.
- **401** — credentials problem: invalid or missing `x-user-key` / `x-api-key`. Re-check with the user; do not loop retries.
- **429** — rate limited; wait the seconds in the `Retry-After` header before retrying. Every response also carries `RateLimit-Limit` / `RateLimit-Remaining` / `RateLimit-Reset` / `RateLimit-Policy` — read `RateLimit-Remaining` to self-throttle *before* you hit a `429`. Remember a shared limit is drained by the whole endpoint group (see the route's Rate limit section), not by this endpoint alone.
- **5xx** — server-side; report it, and if retrying an order use the SAME `x-request-id` so idempotency protects against a double execution.

## Worked Example — "Buy 100$ of BTC"

1. `get-all-routes` → note `baseUrl`; find order creation (`createDemoOrder` / `createRealOrder` — `POST /api/v2/trading/execution/[demo/]orders`). The order identifies the instrument by **exactly one of `symbol` or `instrumentId`** (providing both is rejected), so for a well-known ticker you can pass `symbol` (e.g. `"BTC"`) **directly in the order body — no separate instrument lookup is needed**.
2. (Optional) If you specifically need the numeric `instrumentId`, look it up via `GET https://public-api.etoro.com/api/v1/instruments/BTC` — call `get-route-spec` first to confirm its required scopes and parameters.
3. Ask: demo or real account? Get the user's `x-user-key`.
4. `get-route-spec createRealOrder` (or `createDemoOrder`) → build the body. Identify the instrument with `symbol` (or one resolved `instrumentId`, never both). Size by money uses `amount` (and `orderCurrency`), leaving `units`/`contracts` null:

```json
{
  "action": "open",
  "transaction": "buy",
  "symbol": "BTC",
  "orderType": "mkt",
  "leverage": 1,
  "amount": 100.0,
  "orderCurrency": "usd"
}
```

5. Show the user the full resolved request (`x-user-key` masked) and ask for explicit approval.
6. POST with a fresh `x-request-id` GUID; report `orderId`/`referenceId`; suggest checking the order status via the orders-lookup route or the portfolio route.

## Safety Rules (non-negotiable)

1. **Never execute a money-moving operation without showing the exact request and getting explicit user approval first.**
2. **Never invent credentials, instrument ids, position ids, or order ids** — resolve them via the API or get them from the user.
3. **Default to the demo variant** when the user has not explicitly chosen real.
4. **One order per approval** — do not batch or loop trading operations under a single confirmation.
5. **Mask the user's `x-user-key`** everywhere — commands shown to the user, summaries, error reports.
6. On ambiguous intent (e.g., "close my position" with several open positions), list the options and let the user choose.

## Raw MCP Access (no MCP client configured)

You can call the MCP server directly over HTTP (JSON-RPC). Responses arrive as a server-sent event line (`data: {...}`); the tool payload is in `result.content[0].text`.

List tools:

```bash
curl -sS -X POST https://mcp.public-api.etoro.com \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

Call a tool:

```bash
curl -sS -X POST https://mcp.public-api.etoro.com \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get-route-spec","arguments":{"routeId":"createRealOrder"}}}'
```

Partners: add the `apiKey` tool argument to include partner-only routes, e.g. `"arguments":{"routeId":"createSubAccounts","apiKey":"<partner-x-api-key>"}`.

## Troubleshooting

| Symptom | Cause | Fix |
|---------|-------|-----|
| `get-all-routes` returns an error entry about cache loading | The MCP just started and has not loaded the swagger yet | Retry in a few seconds |
| Route id not found in `get-route-spec` | Route ids change with the API document | Re-run `get-all-routes` and use a current id; partial names and `<METHOD> <path>` also work |
| A route the user expects is missing, and it is a partner operation (KYC, sub-accounts, cash accounts, registration, deposits) | Partner-only routes are hidden without a partner key | Pass the partner application's `x-api-key` as the `apiKey` tool argument (see Partner routes) |
| Tool returns `Forbidden: the supplied apiKey is not a valid eToro partner application x-api-key` | The `apiKey` is unknown or not a partner application key | Retry without `apiKey` for the public catalog; the user should verify the partner key with their eToro contact |
| 401 Unauthorized | Invalid or missing `x-user-key` / `x-api-key` | Re-check the `x-user-key` with the user, and that `x-request-id` is present |
| 401 `malformed x-user-key container (Unterminated string in JSON)` | The `x-user-key` was truncated or altered (e.g. a trailing `_` / `__` dropped, or it was retyped) | Resend the `x-user-key` **verbatim** — exactly as provided; never retype, trim, reformat or line-wrap it |
| 403 Forbidden on an operation | The keys lack the required scope | Call `GET /api/v1/me`, check the `scopes` array, and use / request keys with the needed scope |
| 400 about order size | More than one of `amount`/`units`/`contracts` set | Use exactly one sizing field |
| MCP endpoint unreachable | Wrong URL | Confirm you are using `https://mcp.public-api.etoro.com` |
