# Exploring eToro Social Trading Data

Walk through the social and user data endpoints to build a simple trader leaderboard using the eToro API.

---


Social trading blends market data with **people data**: who is posting, how they have performed, and what the community is discussing. This guide tours the **Social Feeds** and **Users Info** surfaces of the eToro API, then stitches them into a **minimal leaderboard** that ranks traders by a published performance metric.

All REST examples use `https://public-api.etoro.com/api/v1/` and send `x-api-key`, optional `x-user-key` when acting as a logged-in user, and a unique `x-request-id` per call.

## Fetching a public user profile

Start with a known **user ID** or username from the portal or your app. A profile GET returns display name, avatar URL, bio, and flags such as whether the user is a **Pro Investor**.

<!-- skip-test -->
```bash
curl -sS "https://public-api.etoro.com/api/v1/users/12345678/info" \
  -H "x-api-key: $ETORO_API_KEY" \
  -H "x-request-id: $(uuidgen)" \
  -H "accept: application/json"
```

The same call in **JavaScript** is useful when you are assembling leaderboard rows in a serverless function or Node script.

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

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

async function fetchProfile(userId) {
  const res = await fetch(`${BASE}/users/${userId}/info`, {
    headers: {
      "x-api-key": process.env.ETORO_API_KEY,
      "x-user-key": process.env.ETORO_USER_KEY ?? "",
      "x-request-id": randomUUID(),
      accept: "application/json",
    },
  });
  if (!res.ok) throw new Error(`profile ${userId}: ${res.status}`);
  const { data } = await res.json();
  return data;
}
```

## Pulling performance statistics

Leaderboards need numbers. Call a **performance** endpoint that returns time-windowed returns, risk score, and max drawdown. Cache aggressively—many consumers poll too often; respect rate limits and ETag headers if provided.

<!-- skip-test -->
```javascript
async function fetchPerformance(userId) {
  const res = await fetch(
    `${BASE}/users/${userId}/performance?window=12m`,
    {
      headers: {
        "x-api-key": process.env.ETORO_API_KEY,
        "x-user-key": process.env.ETORO_USER_KEY ?? "",
        "x-request-id": randomUUID(),
      },
    }
  );
  if (!res.ok) throw new Error(`performance ${userId}: ${res.status}`);
  const { data } = await res.json();
  return {
    returnPct: data.returnPercent,
    risk: data.riskScore,
    copiers: data.activeCopiers,
  };
}
```

## Reading the social feed

Social feeds provide qualitative context—posts, instruments mentioned, and engagement. Use cursor parameters for pagination and stop when the API returns an empty `nextCursor`.

<!-- skip-test -->
```bash
curl -sS "https://public-api.etoro.com/api/v1/feeds/instruments/100000?limit=20" \
  -H "x-api-key: $ETORO_API_KEY" \
  -H "x-user-key: $ETORO_USER_KEY" \
  -H "x-request-id: $(uuidgen)"
```

In JavaScript, map feed items to `{ userId, text, instruments }` and join against the profile and performance helpers above if you want a “**signal strength**” panel next to raw posts.

<!-- skip-test -->
```javascript
async function fetchFeedPage(instrumentId, cursor) {
  const qs = new URLSearchParams({ limit: "20" });
  if (cursor) qs.set("cursor", cursor);

  const res = await fetch(`${BASE}/feeds/instruments/${instrumentId}?${qs}`, {
    headers: {
      "x-api-key": process.env.ETORO_API_KEY,
      "x-user-key": process.env.ETORO_USER_KEY,
      "x-request-id": randomUUID(),
    },
  });
  if (!res.ok) throw new Error(`feed: ${res.status}`);
  return res.json();
}
```

## Building a simple leaderboard

Given an array of candidate **user IDs** (for example Pro Investors you care about), fetch performance for each, sort by `returnPct` descending, and attach profile names for display. Use `Promise.all` with modest concurrency (e.g. `p-limit` or a manual chunk size) to avoid bursting through rate limits.

<!-- skip-test -->
```javascript
async function buildLeaderboard(userIds) {
  const rows = await Promise.all(
    userIds.map(async (id) => {
      const [profile, perf] = await Promise.all([
        fetchProfile(id),
        fetchPerformance(id),
      ]);
      return {
        id,
        name: profile.displayName,
        return12m: perf.returnPct,
        risk: perf.risk,
        copiers: perf.copiers,
      };
    })
  );

  return rows.sort((a, b) => b.return12m - a.return12m);
}
```

## Errors, rate limits, and retries

Production scripts should treat **429 Too Many Requests** and **5xx** responses as transient: sleep with jitter, respect `Retry-After` when present, and cap total attempts. For **404** on optional resources (for example a user who closed their public profile), degrade gracefully instead of failing the whole leaderboard job. Log the upstream `x-request-id` together with your own correlation id so support can match client logs to server traces.

When you batch profile and performance calls, prefer **fixed concurrency** (five to ten parallel requests is a reasonable starting point) over unbounded `Promise.all` across hundreds of IDs. That pattern keeps latency predictable and reduces the chance of tripping fair-use policies during spikes.

## Responsible use

Social data can include personal information—store only what you need, honor **opt-out** and **regional** restrictions shown in the developer terms, and never present rankings as investment advice. When in doubt, display disclaimers and link to the trader’s full statistics in the eToro client.

From this foundation you can add **copy-trading eligibility** checks, filter by instrument, or combine feed sentiment with your own risk models—still using the same header and base-URL conventions throughout.
