# Managing Watchlists at Scale: Tips and Patterns

Best practices for using the eToro Watchlists API programmatically — curated lists, bulk operations, and organizational patterns.

---


Watchlists are the bridge between **market data** and **human intent**: they group instruments (and sometimes people) so portfolio apps, alerts, and research surfaces stay fast and organized. When you integrate at scale—many users, many lists, frequent updates—you need predictable API usage, idempotent client logic, and a naming strategy that does not collapse under automation. This tutorial focuses on the public API base URL `https://public-api.etoro.com/api/v1/` with **`x-api-key`**, **`x-user-key`**, and **`x-request-id`** on every call.

## Listing and creating watchlists

Start by loading the user’s existing lists with **`GET /watchlists`**. Optional query parameters such as **`itemsPerPageForSingle`**, **`ensureBuiltinWatchlists`**, and **`addRelatedAssets`** let you tune payload size; for background sync jobs, prefer smaller page sizes and explicit pagination over giant single responses.

Creating a list uses **`POST /watchlists`** with query parameters (not a JSON body): at minimum **`name`**, and optionally **`type`** and **`dynamicQuery`** for dynamic lists. Keep names deterministic when your backend creates lists—e.g. `Sector — US Tech — 2026-Q1`—so duplicate cron runs do not spawn dozens of “My List 7” entries.

<!-- skip-test -->
```bash
curl -s -X POST "https://public-api.etoro.com/api/v1/watchlists?name=Core%20Blue%20Chips&type=User" \
  -H "x-request-id: $(uuidgen)" \
  -H "x-api-key: $ETORO_API_KEY" \
  -H "x-user-key: $ETORO_USER_KEY"
```

Rename with **`PUT /watchlists/{watchlistId}`** using the **`newName`** query parameter, and remove stale lists with **`DELETE /watchlists/{watchlistId}`** once you have confirmed nothing else references them.

## Adding and removing instruments

Items are **`WatchlistItemDto`** objects: **`ItemId`** (integer), **`ItemType`** (`Instrument` or `Person`), optional **`ItemRank`**. Use **`POST /watchlists/{watchlistId}/items`** to add, **`PUT`** to reorder or bulk replace (per your integration pattern), and **`DELETE`** to remove. Ranks let you preserve a stable UI order when you sync from an external source of truth.

<!-- skip-test -->
```javascript
import { randomUUID } from "node:crypto";

const BASE = "https://public-api.etoro.com/api/v1";

function headers() {
  return {
    "x-request-id": randomUUID(),
    "x-api-key": process.env.ETORO_API_KEY,
    "x-user-key": process.env.ETORO_USER_KEY,
    "content-type": "application/json",
  };
}

/** @param {{ instrumentId: number, rank?: number }[]} rows */
export async function replaceInstrumentItems(watchlistId, rows) {
  const body = rows.map((r, i) => ({
    ItemId: r.instrumentId,
    ItemType: "Instrument",
    ItemRank: r.rank ?? i + 1,
  }));
  const res = await fetch(`${BASE}/watchlists/${watchlistId}/items`, {
    method: "PUT",
    headers: headers(),
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`watchlist items ${res.status}: ${await res.text()}`);
  return res.json();
}
```

Resolve symbols to **`ItemId`** values via **`GET /market-data/search`** before writing items; never hard-code instrument IDs from a spreadsheet without a periodic reconciliation job.

## Fetching items with pagination

**`GET /watchlists/{watchlistId}`** supports **`pageNumber`** and **`itemsPerPage`**. For large lists, page through until you receive an empty page or a full count from response metadata—do not assume a fixed number of items per page across API versions.

<!-- skip-test -->
```bash
WATCHLIST_ID=12345
curl -s "https://public-api.etoro.com/api/v1/watchlists/${WATCHLIST_ID}?pageNumber=1&itemsPerPage=50" \
  -H "x-request-id: $(uuidgen)" \
  -H "x-api-key: $ETORO_API_KEY" \
  -H "x-user-key: $ETORO_USER_KEY"
```

## Curated lists and public watchlists

Beyond user-owned lists, product teams often surface **editorial** or **community** content. **`GET /curated-lists`** exposes curated collections for discovery experiences. For a specific user’s public lists—after you have a numeric **`userId`** (CID)—use **`GET /watchlists/public/{userId}`** and **`GET /watchlists/public/{userId}/{watchlistId}`**. Map usernames to IDs with **`GET /user-info/people`** when you only start from a handle.

Default-watchlist helpers (`default-watchlist`, `newasdefault-watchlist`, ranking endpoints) matter when your app mirrors eToro’s “primary” list behavior; call them sparingly and only from explicit user actions so you do not fight the user’s own ordering in the mobile app.

## Bulk operations and organization patterns

At scale, treat the API as **eventually consistent** with your internal model: queue bulk updates, **deduplicate** by `(watchlistId, ItemId)`, and use a **single writer** per list to avoid last-write-wins races between a web job and a mobile client.

Practical patterns:

- **Mirror external portfolios**: nightly job resolves symbols → diff against current items → PUT a full ordered set.  
- **Sector buckets**: one watchlist per sector; bulk add after `/instruments` filter queries.  
- **Alert fan-out**: store only `watchlistId` + minimal metadata in your service; fetch items when alerts fire.  

Always backoff on **`429`** responses and log **`x-request-id`** with your internal job ID so support can trace failures. For schema details and additional query flags, refer to the official API Portal—treat these examples as patterns, not an exhaustive contract.
