Use the eToro Rankings API to filter investors, fetch period-scoped performance, and build a cache-friendly leaderboard.
Ranked trader lists no longer need a search call followed by one performance request per username. The dedicated v2 Rankings API returns identity, performance, risk, copier, and activity fields in one paginated response, with server-side filters and sorting.
All ranking calls use https://public-api.etoro.com/api/v2/portfolios/rankings. Send x-api-key, x-user-key, and a fresh x-request-id (UUID) with every request.
The period query parameter is required. This example requests current-year Popular Investors, sorts by gain descending, and excludes risk scores above 6.
import { randomUUID } from "node:crypto";
const RANKINGS_API =
"https://public-api.etoro.com/api/v2/portfolios/rankings";
function rankingsHeaders() {
return {
"x-api-key": process.env.ETORO_API_KEY,
"x-user-key": process.env.ETORO_USER_KEY,
"x-request-id": randomUUID(),
accept: "application/json",
};
}
async function getRankings({ page = 1, pageSize = 25 } = {}) {
const query = new URLSearchParams({
period: "CurrYear",
sort: "-gain",
page: String(page),
pageSize: String(pageSize),
popularInvestor: "true",
riskScoreMax: "6",
});
const response = await fetch(`${RANKINGS_API}?${query}`, {
headers: rankingsHeaders(),
});
if (!response.ok) {
throw new Error(`Rankings failed: ${response.status}`);
}
return response.json();
}
The response contains results and pagination. A ranking row includes username, optional fullName and avatarUrl, gain, riskScore, copiers, aumTier, trading-activity metrics, and resolved sector or industry tags. The gain field is a decimal fraction, so 0.1234 means 12.34%.
function toLeaderboardRows(response) {
return response.results.map((investor, index) => ({
rank: (response.pagination.page - 1) * response.pagination.pageSize + index + 1,
username: investor.username,
displayName: investor.fullName ?? investor.username,
gainPercent: (investor.gain * 100).toFixed(2),
riskScore: investor.riskScore,
copiers: investor.copiers,
aumTier: investor.aumTierDesc ?? investor.aumTier,
tags: investor.tags ?? [],
}));
}
const page = await getRankings({ pageSize: 20 });
console.table(toLeaderboardRows(page));
Use pagination.hasNext to decide whether to request another page. totalItems can be omitted on very large result sets, so do not rely on it to control pagination.
Supported rolling periods include CurrMonth, CurrQuarter, SixMonthsAgo, CurrYear, OneYearAgo, LastYear, and LastTwoYears. Useful filters include:
country using an ISO 3166-1 alpha-2 codegainMin and gainMaxcopiersMin and copiersMaxriskScoreMin and riskScoreMaxpopularInvestoraumTierSort fields use camelCase; prefix the field with - for descending order, such as -copiers or -gain. Unknown sort values return HTTP 400.
When your product already has a curated username list, use the bulk endpoint instead of issuing one request per trader. It accepts up to 100 usernames, preserves their order, and omits missing, private, or unranked users.
async function getKnownTraders(usernames, period = "CurrYear") {
const response = await fetch(
`${RANKINGS_API}/multiple?period=${encodeURIComponent(period)}`,
{
method: "POST",
headers: {
...rankingsHeaders(),
"content-type": "application/json",
},
body: JSON.stringify({ usernames: usernames.slice(0, 100) }),
}
);
if (!response.ok) {
throw new Error(`Bulk rankings failed: ${response.status}`);
}
const { results } = await response.json();
return results.map(({ username, value }) => ({
username,
gainPercent: (value.gain * 100).toFixed(2),
riskScore: value.riskScore,
copiers: value.copiers,
}));
}
GET /api/v2/portfolios/rankings/presets lists the available preset names. Apply one with GET /api/v2/portfolios/rankings/presets/{type}; examples include names such as top-gainers and low-risk. Use the list endpoint instead of hard-coding preset availability.
For custom discovery screens, GET /api/v2/portfolios/rankings/tags returns the industry and sector catalog, while GET /api/v2/portfolios/rankings/summary?summary=industries aggregates the ranking universe by a chosen dimension.
Rankings use the default shared quota, so requests to other endpoints without a dedicated limit draw from the same budget. Cache pages behind your own API, honor RateLimit-* and Retry-After response headers, and avoid refreshing on every keystroke.
Public ranking rows are privacy-aware: names and avatars may be absent, and private or unranked users may return 404 or be omitted from bulk results. Always fall back to username, tolerate missing optional fields, and do not present rankings as investment advice.
Was this helpful?
Walk through the social and user data endpoints to build a simple trader leaderboard using the eToro API.
See what the eToro community is actually buying and holding — distilled into one live score.
Be the first to know when we publish new API guides, product updates, and builder resources.
Newsletter coming soon. We'll only email you when it launches.