---
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`

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** + ALL routes as `routeId -> "<METHOD> <path> - <summary>"`, plus a `requiredScopes` map (routeId -> scopes, any one grants access) |
| `get-route-spec` | Returns the full OpenAPI spec for one route: parameters, request body, responses, referenced schemas, an **Authentication** section, and the **Required scopes** for the route |

## 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`.
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`.
2. Read carefully:
   - **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`.
   - `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.

### 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 before retrying.
- **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 instrument lookup (`GET /api/v1/instruments/{symbol}`) and order creation (`createDemoOrder` / `createRealOrder` — `POST /api/v2/trading/execution/[demo/]orders`).
2. `get-route-spec` for the instrument route → `GET https://public-api.etoro.com/api/v1/instruments/BTC` (read-only; still requires the keys + `x-request-id`) → extract `instrumentId` from the response.
3. Ask: demo or real account? Get the user's `x-user-key`.
4. `get-route-spec createRealOrder` (or `createDemoOrder`) → build the body. Size by money uses `amount` (and `orderCurrency`), leaving `units`/`contracts` null:

```json
{
  "action": "open",
  "transaction": "buy",
  "instrumentId": <resolved BTC instrumentId>,
  "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"}}}'
```

## 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 |
| 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 |
| 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` |
