Get startedDashboard

Index membership

List the members of the S&P 500, Nasdaq-100 or Dow Jones Industrial Average today or on any past date.

GET/v1/index/{index_name}
View llms.txt

GET /v1/index/{index_name} returns the members of an index on one date: today's roster by default, or the roster on any past date with as_of. Each row is one member, with its entity_id, ticker, name, the first and last day of its membership, and its grain. Past rosters are survivorship-free: they include members that were later removed, acquired or delisted.

Three indexes are served: the S&P 500 (sp500), the Dow Jones Industrial Average (djia) and the Nasdaq-100 (ndx100). All three carry a current roster that refreshes daily.

The response contains no money, share or percentage values. Dates are YYYY-MM-DD strings.

Each row states its own grain in member_grain. On security rows the entity_id names one share class and joins directly to Prices; on issuer rows it names the company and ticker is null. Tickers and entities explains the two. For the dated additions and removals behind the rosters, use Index events.

Parameters

index_namestrRequired

Path segment. sp500, djia or ndx100, or one of the alternative names below. Case-insensitive. Any other name returns 400.

as_ofstrDefault: today (current roster)

YYYY-MM-DD, a real calendar date. Returns the members on that date, including members whose removed_date is that date. Without as_of, returns the current members. No history limit applies on any plan.

limitintDefault: 1000

Rows per page, 1 to 1,000, on every plan.

offsetintDefault: 0

Rows to skip before the page starts. 0 or more.

Accepted names and alternative names, all case-insensitive:

  • sp500: SP500, SPX, GSPC, SP, SNP500, SANDP, S&P, S&P500.
  • djia: DJIA, DJI, DOW, DOW30, DOWJONES.
  • ndx100: NDX100, NDX, NASDAQ, NASDAQ100.

The & in S&P and S&P500 must reach the API as a literal character; the percent-encoded form S%26P500 returns 400.

Response

The body is {"data": [...], "meta": {...}}. Rows are ordered by entity_name, with rows that have no name last, then by each member's own key, so two members with the same name, such as Alphabet's GOOG and GOOGL, each appear once across pages.

Row fields

fieldtypeunitnullabledescription
entity_idint—yesShare-class id when member_grain is security; company id when it is issuer. Null for a member that is not linked to an entity.
tickerstr—yesWith as_of: the ticker recorded for the membership, or else the ticker in force on the date. Without as_of: today's ticker. Null on issuer rows and where no ticker is on record.
entity_namestr—yesWith as_of: the name recorded for the membership, or else the current name. Without as_of: the current name. Can be null for a member that is not linked to an entity.
added_datestrdatenoFirst day in the index.
removed_datestrdateyesLast day in the index. Null while the entity is a member.
member_grainstr—nosecurity or issuer.
ticker_basisstr—nofirst_known when no ticker was in force on the date and ticker is the member's earliest ticker on record. Absent otherwise.

added_date and removed_date are index dates as the index publisher dates the change, not trading dates. Both are inclusive: a member is in the roster on its removed_date.

Meta

Response metadata explains knowledge_time and freshness.

fieldtypeunitnullabledescription
indexstr—noSP500, DJIA or NDX100.
as_ofstrdatenoDate of the roster: as_of, or today (UTC).
data_throughstrdatenoDate of the newest addition or removal on record for the index.
entity_id_grainstr—nosecurity. Each row's own grain is in member_grain.
coverage_endstrdatenoLast date the index's record covers.
countintrowsnoRows in this page.
totalintrowsnoMembers in the roster across all pages.
has_morebool—notrue while offset + count is less than total.
knowledge_timestrUTC timestampyesNewest knowledge time of the membership data.
freshnessarray—noOne index_membership entry; its through equals coverage_end.

Pagination

The endpoint pages by offset. Pass limit (up to 1,000) and offset; meta.has_more is true while rows remain and meta.total counts the whole roster. Every roster shown on this page fits in one request at the default limit of 1,000. Pagination compares offset and cursor paging.

The last page of today's S&P 500 roster:

Terminal
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/sp500?limit=2&offset=501"
response.json
{
  "data": [
    {
      "entity_id": 10029566,
      "ticker": "ZBH",
      "entity_name": "ZIMMER BIOMET HOLDINGS INC",
      "added_date": "2001-08-07",
      "removed_date": null,
      "member_grain": "security"
    },
    {
      "entity_id": 10002945,
      "ticker": "ZTS",
      "entity_name": "ZOETIS INC",
      "added_date": "2013-06-24",
      "removed_date": null,
      "member_grain": "security"
    }
  ],
  "meta": {
    "index": "SP500",
    "as_of": "2026-09-27",
    "data_through": "2026-09-21",
    "coverage_end": "2026-09-26",
    "count": 2,
    "total": 503,
    "has_more": false,
    ...
  }
}

Errors

statuserror codewhen it happenshow to fix
400bad_requestindex_name is not a served index or alternative name. The message lists the available names.Use sp500, djia, ndx100 or an alternative name above.
400bad_requestas_of is not a real calendar date in YYYY-MM-DD form, for example 2000-1-3 or 2026-02-30.Pass a real date as YYYY-MM-DD.
400bad_requestdjia or ndx100 with an as_of after the index's coverage_end.Pass a date on or before coverage_end, or omit as_of for the current roster.
400bad_requestlimit is not a whole number of 1 or more, or is more than 1,000.Pass a limit from 1 to 1,000.
400bad_requestoffset is negative or not a whole number.Pass 0 or a positive whole number.
401unauthorizedNo key, or a key that is not valid.Send X-API-Key: YOUR_API_KEY.
429asof_limit_exceededFree plan: more than 50 distinct as_of dates in one UTC day.Wait until midnight UTC, or use a paid plan.
429see Rate limitsA request or row allowance is used up.Wait for the reset. A request-limit body gives the wait in retry_after_seconds; daily allowances reset at 00:00 UTC and hourly ones at the top of the UTC hour.

Unknown index (/v1/index/foo):

response.json
{"error": "bad_request", "code": "bad_request", "status": 400, "detail": "Unknown index: foo. Available: djia, ndx100, sp500"}

as_of past coverage_end (/v1/index/djia?as_of=2099-12-31). The date in detail is the index's coverage_end on the day of the request:

response.json
{"error": "bad_request", "code": "bad_request", "status": 400, "detail": "djia membership is recorded through 2026-10-07. Request a date on or before 2026-10-07."}

Malformed as_of (as_of=2000-1-3):

response.json
{"error": "bad_request", "code": "bad_request", "status": 400, "detail": "Invalid as_of date format (YYYY-MM-DD)"}

An impossible date (as_of=2026-02-30):

response.json
{"error": "bad_request", "code": "bad_request", "status": 400, "detail": "Invalid as_of date: '2026-02-30' (expected a real YYYY-MM-DD date)"}

limit=0 and limit=1001:

response.json
{"error": "bad_request", "code": "bad_request", "status": 400, "detail": "limit must be a positive integer"}
response.json
{"error": "bad_request", "code": "bad_request", "status": 400, "detail": "limit max is 1000"}

offset=-1:

response.json
{"error": "bad_request", "code": "bad_request", "status": 400, "detail": "offset must be a non-negative integer"}

No key:

response.json
{"error": "unauthorized", "code": "unauthorized", "status": 401, "detail": "No API key was sent. Pass your key in the X-API-Key header or the ?api_key= query parameter. Get one free at https://xfinlink.com/signup", "message": "No API key was sent. Pass your key in the X-API-Key header or the ?api_key= query parameter. Get one free at https://xfinlink.com/signup"}

The 429 for the Free as_of limit has this body:

response.json
{"error":"asof_limit_exceeded","code":"asof_limit_exceeded","status":429,"detail":"Historical index query limit reached. Contact hello@xfinlink.com for extended access."}

Plan differences

A key is required on every plan. Rosters are the same on every plan, and as_of reaches the start of each index's record on every plan, including Free.

  • Free: full history with as_of and limit up to 1,000. After more than 50 distinct as_of dates in one UTC day, every roster request with as_of returns 429 for the rest of that day, including dates already requested. Requests without as_of do not count.
  • Pro: identical rosters, no limit on distinct as_of dates.
  • Max: identical to Pro.
  • Redistribution: identical to Pro.
  • No key: 401.

Each request counts as one request against your plan's daily allowance. The 2000-01-03 roster with a Free key returns all 500 members, the same as Pro:

Terminal
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/sp500?as_of=2000-01-03&limit=3"
response.json
{
  "data": [
    {
      "entity_id": 10019719,
      "ticker": "COMS",
      "entity_name": "3COM CORP",
      "added_date": "1996-01-02",
      "removed_date": "2000-07-27",
      "member_grain": "security"
    },
    ...
  ],
  "meta": {
    "index": "SP500",
    "as_of": "2000-01-03",
    "data_through": "2026-09-21",
    "coverage_end": "2026-09-26",
    "count": 3,
    "total": 500,
    "has_more": true,
    ...
  }
}

Edge cases

Survivorship-free rosters

A past roster includes members that later left the index. The S&P 500 on 2000-01-03 has 500 members, among them Enron and WorldCom. Use entity_id to join a roster to Prices or Fundamentals.

Labels on past dates

With as_of, ticker and entity_name are those recorded for the membership, so a company can appear under an earlier ticker. On 1930-01-02, Exxon Mobil's security appears as J and Chevron's as SD, each with ticker_basis: "first_known":

Terminal
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/sp500?as_of=1930-01-02"
response.json
{
  "data": [
    ...
    {
      "entity_id": 10003589,
      "ticker": "SD",
      "entity_name": "CHEVRON CORP NEW",
      "added_date": "1925-12-31",
      "removed_date": null,
      "member_grain": "security",
      "ticker_basis": "first_known"
    },
    ...
    {
      "entity_id": 10001434,
      "ticker": "J",
      "entity_name": "EXXON MOBIL CORP",
      "added_date": "1925-12-31",
      "removed_date": null,
      "member_grain": "security",
      "ticker_basis": "first_known"
    },
    ...
  ],
  "meta": {
    "index": "SP500",
    "as_of": "1930-01-02",
    "data_through": "2026-09-21",
    "coverage_end": "2026-09-26",
    "count": 90,
    "total": 90,
    "has_more": false,
    ...
  }
}

Without as_of, the roster uses today's labels. entity_id is the same in both.

Removal dates are inclusive

removed_date is the member's last day in the index. Comerica left the S&P 500 with removed_date 2024-06-21 and is in the roster on that date; CrowdStrike joined on 2024-06-24:

Terminal
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/sp500?as_of=2024-06-21"
response.json
{
  "data": [
    ...
    {
      "entity_id": 10012303,
      "ticker": "CMA",
      "entity_name": "COMERICA INC",
      "added_date": "1995-12-01",
      "removed_date": "2024-06-21",
      "member_grain": "security"
    },
    ...
  ],
  "meta": {
    "index": "SP500",
    "as_of": "2024-06-21",
    "total": 503,
    ...
  }
}
Terminal
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/sp500?as_of=2024-06-24"
response.json
{
  "data": [
    ...
    {
      "entity_id": 10006907,
      "ticker": "CRWD",
      "entity_name": "CROWDSTRIKE HOLDINGS INC",
      "added_date": "2024-06-24",
      "removed_date": null,
      "member_grain": "security"
    },
    ...
  ],
  "meta": {
    "index": "SP500",
    "as_of": "2024-06-24",
    "total": 503,
    ...
  }
}

Share classes and issuer members

An index that lists two share classes of one company returns two rows. Alphabet appears as GOOGL (Class A) and GOOG (Class C) in the S&P 500 and the Nasdaq-100. Today's S&P 500 roster has 503 rows because Alphabet, Fox (FOXA, FOX) and News Corp (NWSA, NWS) each have two classes in the index. A member recorded at company grain has member_grain: "issuer" and a null ticker. A member not linked to an entity has a null entity_id, and its ticker and entity_name carry what is recorded for the membership, which can be null. Drop rows with a null entity_id before joining to other endpoints.

Terminal
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/ndx100"
response.json
{
  "data": [
    ...
    {
      "entity_id": 10030636,
      "ticker": "GOOGL",
      "entity_name": "ALPHABET INC",
      "added_date": "2005-12-21",
      "removed_date": null,
      "member_grain": "security"
    },
    {
      "entity_id": 10003590,
      "ticker": "GOOG",
      "entity_name": "ALPHABET INC",
      "added_date": "2014-04-03",
      "removed_date": null,
      "member_grain": "security"
    },
    ...
  ],
  "meta": {
    "index": "NDX100",
    "as_of": "2026-09-27",
    "data_through": "2026-08-03",
    "coverage_end": "2026-09-26",
    "count": 102,
    "total": 102,
    "has_more": false,
    ...
  }
}

Dates before coverage starts

Each index's record opens on a fixed date: sp500 on 1925-12-31, ndx100 on 1995-01-01 and djia on 1999-10-31. Index events reports the same date as meta.event_coverage_start.

For sp500 and ndx100, an as_of before the record opens returns an empty roster:

Terminal
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/sp500?as_of=1925-12-30&limit=2"
response.json
{
  "data": [],
  "meta": {
    "index": "SP500",
    "as_of": "1925-12-30",
    "data_through": "2026-09-21",
    "coverage_end": "2026-09-26",
    "count": 0,
    "total": 0,
    "has_more": false,
    ...
  }
}

On the opening date the founding roster is returned, with added_date equal to that date (/v1/index/sp500?as_of=1925-12-31&limit=2):

response.json
{
  "data": [
    {
      "entity_id": 10037196,
      "ticker": null,
      "entity_name": "ABITIBI POWER & PAPER LTD",
      "added_date": "1925-12-31",
      "removed_date": "1932-10-01",
      "member_grain": "security"
    },
    ...
  ],
  "meta": {
    "index": "SP500",
    "as_of": "1925-12-31",
    "data_through": "2026-09-21",
    "coverage_end": "2026-09-26",
    "count": 2,
    "total": 89,
    "has_more": true,
    ...
  }
}

The same boundary for ndx100, with as_of=1994-12-31 and then as_of=1995-01-01 (limit=2):

response.json
{
  "data": [],
  "meta": {
    "index": "NDX100",
    "as_of": "1994-12-31",
    "data_through": "2026-08-03",
    "coverage_end": "2026-09-26",
    "count": 0,
    "total": 0,
    "has_more": false,
    ...
  }
}
response.json
{
  "data": [
    {
      "entity_id": 10019719,
      "ticker": "COMS",
      "entity_name": "3COM CORP",
      "added_date": "1995-01-01",
      "removed_date": "2001-12-23",
      "member_grain": "security"
    },
    ...
  ],
  "meta": {
    "index": "NDX100",
    "as_of": "1995-01-01",
    "data_through": "2026-08-03",
    "coverage_end": "2026-09-26",
    "count": 2,
    "total": 100,
    "has_more": true,
    ...
  }
}

For djia, members of the founding roster keep their original added_date, so an as_of before 1999-10-31 returns the members of that roster who were already in the index on the date. On 1990-01-02 that is 23 rows:

Terminal
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/djia?as_of=1990-01-02"
response.json
{
  "data": [
    {
      "entity_id": 10010089,
      "ticker": "MMM",
      "entity_name": "3M CO",
      "added_date": "1976-08-09",
      "removed_date": null,
      "member_grain": "security"
    },
    {
      "entity_id": 10003045,
      "ticker": "MO",
      "entity_name": "ALTRIA GROUP INC",
      "added_date": "1985-10-30",
      "removed_date": "2008-02-18",
      "member_grain": "security"
    },
    {
      "entity_id": 10017034,
      "ticker": "AXP",
      "entity_name": "AMERICAN EXPRESS CO",
      "added_date": "1982-08-30",
      "removed_date": null,
      "member_grain": "security"
    },
    ...
  ],
  "meta": {
    "index": "DJIA",
    "as_of": "1990-01-02",
    "data_through": "2026-06-29",
    "coverage_end": "2026-09-26",
    "count": 23,
    "total": 23,
    "has_more": false,
    ...
  }
}

Dates after coverage ends

meta.coverage_end is the last date each record covers. For djia and ndx100, an as_of after it returns 400 (see Errors). For sp500, a later as_of returns the latest roster on record:

Terminal
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/sp500?as_of=2027-01-04&limit=2"
response.json
{
  "data": [
    {
      "entity_id": 10010089,
      "ticker": "MMM",
      "entity_name": "3M CO",
      "added_date": "1957-03-01",
      "removed_date": null,
      "member_grain": "security"
    },
    ...
  ],
  "meta": {
    "index": "SP500",
    "as_of": "2027-01-04",
    "data_through": "2026-09-21",
    "coverage_end": "2026-09-26",
    "count": 2,
    "total": 503,
    "has_more": true,
    ...
  }
}

Request examples

The first two rows of the S&P 500 on 2000-01-03. The Python client returns a pandas.DataFrame of the rows and, because more rows exist, emits a UserWarning that 2 of 500 rows were returned.

request.pycurlprompt
import xfinlink as xfl

xfl.set_api_key("YOUR_API_KEY")

df = xfl.index("sp500", as_of="2000-01-03", limit=2)
print(df.to_string(index=False))
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/sp500?as_of=2000-01-03&limit=2"
Which companies were in the S&P 500 on 3 January 2000? Show me the first two.
stdoutresponse.jsontool result
 entity_id ticker entity_name added_date removed_date member_grain
  10019719   COMS   3COM CORP 1996-01-02   2000-07-27     security
  10010089    MMM       3M CO 1957-03-01          NaN     security
{
  "data": [
    {
      "entity_id": 10019719,
      "ticker": "COMS",
      "entity_name": "3COM CORP",
      "added_date": "1996-01-02",
      "removed_date": "2000-07-27",
      "member_grain": "security"
    },
    {
      "entity_id": 10010089,
      "ticker": "MMM",
      "entity_name": "3M CO",
      "added_date": "1957-03-01",
      "removed_date": null,
      "member_grain": "security"
    }
  ],
  "meta": {
    "index": "SP500",
    "as_of": "2000-01-03",
    "data_through": "2026-09-21",
    "entity_id_grain": "security",
    "coverage_end": "2026-09-29",
    "count": 2,
    "total": 500,
    "has_more": true,
    "knowledge_time": "2026-09-29T18:57:20Z",
    "freshness": [
      {
        "dataset": "index_membership",
        "through": "2026-09-29",
        "promoted_at": "2026-09-29T19:45:53Z"
      }
    ]
  }
}
{"tool": "get_index", "arguments": {"index_name": "sp500", "as_of": "2000-01-03", "limit": 2}}
{
  "data": [
    {
      "entity_id": 10019719,
      "ticker": "COMS",
      "entity_name": "3COM CORP",
      "added_date": "1996-01-02",
      "removed_date": "2000-07-27",
      "member_grain": "security"
    },
    {
      "entity_id": 10010089,
      "ticker": "MMM",
      "entity_name": "3M CO",
      "added_date": "1957-03-01",
      "removed_date": null,
      "member_grain": "security"
    }
  ],
  "meta": {
    "index": "SP500",
    "as_of": "2000-01-03",
    "data_through": "2026-09-21",
    "entity_id_grain": "security",
    "coverage_end": "2026-09-29",
    "count": 2,
    "total": 500,
    "has_more": true,
    "knowledge_time": "2026-09-29T18:57:20Z",
    "freshness": [
      {
        "dataset": "index_membership",
        "through": "2026-09-29",
        "promoted_at": "2026-09-29T19:45:53Z"
      }
    ]
  }
}

Was this page helpful?

request.pycurlprompt
import xfinlink as xfl

xfl.set_api_key("YOUR_API_KEY")

df = xfl.index("sp500", as_of="2000-01-03", limit=2)
print(df.to_string(index=False))
curl -s -H "X-API-Key: YOUR_API_KEY" \
  "https://api.xfinlink.com/v1/index/sp500?as_of=2000-01-03&limit=2"
Which companies were in the S&P 500 on 3 January 2000? Show me the first two.
stdoutresponse.jsontool result
 entity_id ticker entity_name added_date removed_date member_grain
  10019719   COMS   3COM CORP 1996-01-02   2000-07-27     security
  10010089    MMM       3M CO 1957-03-01          NaN     security
{
  "data": [
    {
      "entity_id": 10019719,
      "ticker": "COMS",
      "entity_name": "3COM CORP",
      "added_date": "1996-01-02",
      "removed_date": "2000-07-27",
      "member_grain": "security"
    },
    {
      "entity_id": 10010089,
      "ticker": "MMM",
      "entity_name": "3M CO",
      "added_date": "1957-03-01",
      "removed_date": null,
      "member_grain": "security"
    }
  ],
  "meta": {
    "index": "SP500",
    "as_of": "2000-01-03",
    "data_through": "2026-09-21",
    "entity_id_grain": "security",
    "coverage_end": "2026-09-29",
    "count": 2,
    "total": 500,
    "has_more": true,
    "knowledge_time": "2026-09-29T18:57:20Z",
    "freshness": [
      {
        "dataset": "index_membership",
        "through": "2026-09-29",
        "promoted_at": "2026-09-29T19:45:53Z"
      }
    ]
  }
}
{"tool": "get_index", "arguments": {"index_name": "sp500", "as_of": "2000-01-03", "limit": 2}}
{
  "data": [
    {
      "entity_id": 10019719,
      "ticker": "COMS",
      "entity_name": "3COM CORP",
      "added_date": "1996-01-02",
      "removed_date": "2000-07-27",
      "member_grain": "security"
    },
    {
      "entity_id": 10010089,
      "ticker": "MMM",
      "entity_name": "3M CO",
      "added_date": "1957-03-01",
      "removed_date": null,
      "member_grain": "security"
    }
  ],
  "meta": {
    "index": "SP500",
    "as_of": "2000-01-03",
    "data_through": "2026-09-21",
    "entity_id_grain": "security",
    "coverage_end": "2026-09-29",
    "count": 2,
    "total": 500,
    "has_more": true,
    "knowledge_time": "2026-09-29T18:57:20Z",
    "freshness": [
      {
        "dataset": "index_membership",
        "through": "2026-09-29",
        "promoted_at": "2026-09-29T19:45:53Z"
      }
    ]
  }
}