lseg-data

SkillCommerce & finance

Use when "query LSEG/Refinitiv", "fundamentals or market data from LSEG", "ESG scores", "management guidance / guidance reports / GR", "RIC/ISIN symbology", "corporate governance or activism (poison pills, campaigns)", "M&A or IPO deals", "syndicated loans or project finance", "PE/VC investments", "joint ventures", "municipal bonds", "Lipper fund details", "stock screening (fscreen)", "Refinitiv news", "Workspace web client", "Codebook", or any use of the `lseg.data` Python API. (academic loan/PE data: prefer the wrds skill.)

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the lseg-data skill

What this skill tells your AI

The instructions your AI receives, as published by edwinhu/workflows in skills/lseg-data/SKILL.md and read by ahel’s review.

What this skill carries — grep references/ for any subject the names below miss: !d=${CLAUDE_SKILL_DIR}; command -v skill-toc >/dev/null 2>&1 && exec skill-toc "$d"; s=$HOME/.claude/skills/plugin-utils/bin/skill-toc; [ -x "$s" ] && exec "$s" "$d"; echo "(skill-toc unavailable: references and scripts are NOT listed here — install the plugin-utils plugin, or start a new session so its bin/ reaches PATH)"

Contents

  • Access Paths
  • MCP Connector
  • Query Enforcement
  • Quick Start
  • Authentication
  • Core APIs
  • Key Field Prefixes
  • RIC Symbology
  • Rate Limits
  • Additional Resources

LSEG Data Library

Access financial data from LSEG (London Stock Exchange Group), formerly Refinitiv, via the lseg.data Python library or by driving the Workspace web client over CDP.

Access Paths

Pick the lowest-numbered path that can serve the request.

#PathUse forAuthReference
1lseg.data Python libraryanything it covers; batch and production workRDP machine credentialsthis file + references/*
2Token-lift → RDP REST from Pythonthe same data with no machine credentials — borrows the browser sessionWorkspace tab's edp-tokenreferences/workspace-web-cdp.md
3In-page fetch() on the target originWorkspace-internal endpoints only (SDC deal universes, FSCREEN)browser cookiesreferences/workspace-web-cdp.md
4LSEG MCP connector (mcp__claude_ai_LSEG__*)nothing on this account — every scope family is deniedLSEG-side SSOthis file, MCP Connector below

Path 1 remains preferred where it works — pip install lseg-data is available on every platform. Paths 2 and 3 exist because some data is only reachable through the web client, and because the token-lift avoids needing machine credentials at all.

The desktop Workspace app is Windows/macOS only. On Linux there is no desktop session and no Electron binary to launch with --remote-debugging-port; the web client at https://workspace.refinitiv.com/web is the only Workspace surface. Never emit a session.desktop.workspace config or an app path on Linux.

The helper for paths 2 and 3 is scripts/workspace_cdp.py:

python3 scripts/workspace_cdp.py token       # verify the browser session
python3 scripts/workspace_cdp.py datagrid --universe AAPL.O,MSFT.O \
        --fields TR.CommonName,TR.Revenue
import sys; sys.path.insert(0, "scripts")
import workspace_cdp as w
df = w.datagrid_df(["AAPL.O"], ["TR.Revenue", "TR.Revenue.fperiod"],
                   {"SDate": "0", "EDate": "-4", "Frq": "FY"})

Requires Chromium on CDP port 9222 with a signed-in Workspace Web tab — see the browser-automation skill for the browser, and references/workspace-web-cdp.md for session setup.

MCP Connector — installed, entitled to nothing

The LSEG connector for Claude registers ~50 mcp__claude_ai_LSEG__* tools. On this account every one of them is refused. Measured 2026-09-09, seven tools across seven distinct scope families, each a first call with valid arguments:

ToolDenied scope
entity_search (action='schema')search (API_ACCESS_CONTROL/MCP_DATA_ON)
historical_pricing_summariesttsc_historical_pricing_summaries (API_ACCESS_CONTROL/MCP_DATA_ON)
qa_company_fundamentalsqa (API_ACCESS_CONTROL/MCP_DATA_QA_ON)
news_nl_searchnews_ai (API_ACCESS_CONTROL/MCP_DATA_MRN_ON)
transcriptstranscripts (API_ACCESS_CONTROL/MCP_DATA_TRANSCRIPTS_ON)
fx_spot_pricefx_spot (LFA_API/EP_FXSPOTS)
ixm_list_indexesixm (FTSER_MCP/FTSER_MCP)
  • The connector's presence in the toolset is not access. Denial arrives as a formatted 🔒 Entitlement Error, not an exception — cheap to probe, and it names the exact flag. Unlike the datagrid failures above, this one is loud and honest.
  • Do not route an LSEG request here. Paths 1-3 are the working surfaces; a connector call spends a round trip to learn nothing. Re-probe only after the user says entitlements changed.
  • The denials are per-family toggles, and the flag names are the ask. MCP_DATA_ON, MCP_DATA_QA_ON, MCP_DATA_MRN_ON, MCP_DATA_TRANSCRIPTS_ON are separate switches on the LSEG account. Enabling them is an account-side request to the LSEG rep, quoting the flag — nothing in this repo can unblock it.
  • MCP_DATA_MRN_ON is the one worth asking for. News is 403 insufficient_scope on the platform session and unentitled on the lifted edp-token too, so it is the one content set with no working path at all; the connector would be a new capability rather than a duplicate. Everything else it offers — fundamentals, IBES, pricing, symbology — path 1 already serves.

Query Enforcement

IRON LAW: NO DATA CLAIM WITHOUT SAMPLE INSPECTION

Before claiming ANY LSEG query succeeded, follow these steps:

  1. VALIDATE field names exist (check prefixes: TR., CF_)
  2. VALIDATE RIC symbology is correct (.O, .N, .L, .T)
  3. EXECUTE the query
  4. INSPECT sample rows with .head() or .sample()
  5. VERIFY critical columns are not NULL
  6. VERIFY date range matches expectations
  7. CLAIM success only after all checks pass

This is not negotiable. Skipping result inspection is NOT HELPFUL — the user builds analysis on data with undetected quality problems.

LSEG API Facts

  • The API does not raise errors for invalid field names or wrong RICs — it returns empty results or NULL columns. Treating returned rows as correct data is an unverified claim presented as fact: inspect for NULLs, wrong dates, and invalid values before returning anything.
  • Field-name typos are common and fail silently (TR.EPS vs TR.Eps). Validate field names against the documentation before executing.
  • User-supplied RICs often carry the wrong exchange suffix. Verify against the RIC Symbology section (.O, .N, .L, .T) before querying.
  • Market data has T-1 availability — today's data arrives tomorrow. Querying through today produces silent gaps; see the Date Awareness section.
  • ONE invalid field name in a list silently truncates the WHOLE result. TR.HoldingsValueHeld does not exist: alone it raises Unable to resolve all requested fields. Mixed with four valid fields it raises nothing — it is dropped from the columns AND the result collapses from 8,298 holder rows to 1 (AAPL.O; 16 -> 1 on SWDR.PK). The dataframe has exactly the columns you asked for, no warning, and a fraction of a percent of the rows. Validate each field name alone before batching, and assert an expected row count. Measured 2026-09-05.
  • The default http.request-timeout is 20 seconds, and on text-heavy content it — not a row cap or an entitlement — is what raises ReadTimeout. Five instruments of guidance failed at the default and returned 66,447 rows at ld.get_config().set_param("http.request-timeout", 180), which must be called BEFORE the session opens. Reaching for a smaller batch first wastes the run.
  • A 403 enumerates the platform token's entitlements. Insufficient scope names the required scope, the full Available scopes set, and the missing one — so probing one unentitled endpoint (e.g. /data/news/v1/headlines) reveals what the account actually holds. The browser-token caveat below, that entitlements cannot be enumerated, applies to the lifted edp-token, not to this path.
  • Rate limits bind per session (500 requests/minute) and per request (get_data() 10,000 data points, get_history() 3,000 rows) — many small queries still hit the session cap. Batch instead of looping.

Workspace Web / CDP Facts

  • The lifted edp-token lives ~10 minutes. A script that reads it once and runs for an hour dies mid-batch with a 401. workspace_cdp.token() re-reads within 60s of expiry — use it per request rather than caching the string.
  • Entitlements are per-account and cannot be enumerated — the token's scopes are encrypted inside the JWT. On the verified account, news is 403 insufficient_scope and CUSIP/ISIN symbology is unentitled. Probe the endpoint and read the error; never assume a dataset is available because it exists in the docs.
  • A 200 is not proof of success. Symbology returns HTTP 200 with a per-identifier errors array when unentitled, and datagrid returns null data with messages.codes of -2 ("empty") for fields that do not apply. Read errors and messages.codes, not just the status line.
  • Cross-origin fetch() from the Workspace tab is CORS-blocked and fails as a bare "Failed to fetch" that looks like a network outage. Workspace-internal endpoints must be called from a tab on their own origin — that is what in_page_fetch() does.
  • Deal-level SCREEN(U(IN(DEALS)) ...) universes are rejected by the public datagrid (error 218) and only work through the internal datacloud endpoint via path 3.
  • Codebook could not execute code on the one account/machine tested — the kernel WebSocket is killed before it reaches JupyterHub (no handshake response; a bogus kernel ID fails identically to a valid one), including through Codebook's own JupyterLab UI. Do not debug headers or subprotocols; the two things actually worth trying are a full session reset (clear site data + re-login) and a supported browser. Its contents/kernels REST API does work. See references/codebook.md.

Red Flags — STOP If About To:

  • Execute a query without validating field names and RIC suffixes first → STOP. The API will not error for you.
  • Return a dataframe without .head() or .sample() inspection → STOP. Handing over uninspected data gives the user undetected quality problems — unhelpful on its own terms.
  • Write a session.desktop.workspace config, or reference /Applications/Refinitiv Workspace.app, on Linux → STOP. There is no desktop session on this platform; the config will fail at connect time.
  • Reach for an mcp__claude_ai_LSEG__* tool → STOP. Every scope family is denied on this account; use path 1, 2 or 3.
  • Navigate or reload the user's signed-in Workspace tab → STOP. It destroys their layout and can drop the session that every CDP path depends on. Open your own tab instead.

Data Validation Checklist

Before EVERY data retrieval claim, verify the following:

For ld.get_data() (fundamentals/ESG):

  • Field names use correct prefix (TR. for Refinitiv)
  • RIC symbology verified (correct exchange suffix)
  • Result inspection: .head() or .sample() executed
  • NULL check on critical fields (e.g., revenue, EPS)
  • Row count verification (is result size reasonable?)
  • Date context verified (fiscal periods, as-of dates)

For ld.get_history() (time series):

  • Field names are valid (OPEN, HIGH, LOW, CLOSE, VOLUME, or CF_ prefixes)
  • Start/end dates specified explicitly
  • Date range adjusted for T-1 availability (market data lag)
  • Result inspection: check first and last rows
  • NULL check on OHLCV fields
  • Date continuity check (gaps in trading days expected, but not in date sequence)

For symbol_conversion.Definition() (mapping):

  • Input identifier type specified correctly
  • Result inspection: verify mapped values exist
  • NULL check (some securities may not have all identifiers)

For ALL queries:

  • Rate limits considered (batch if >10k data points)
  • Session management: open_session() at start, close_session() at end
  • Error handling: try/except for network failures
  • Sample inspection BEFORE claiming data is ready

Quick Start

To get started with LSEG Data Library, initialize a session and execute queries:

import lseg.data as ld

# Initialize session
ld.open_session()

# Get fundamentals
df = ld.get_data(
    universe=[‘AAPL.O’, ‘MSFT.O’],
    fields=[‘TR.CompanyName’, ‘TR.Revenue’, ‘TR.EPS’]
)
print(df.head())  # Inspect sample data

# Get historical prices
prices = ld.get_history(
    universe=’AAPL.O’,
    fields=[‘OPEN’, ‘HIGH’, ‘LOW’, ‘CLOSE’, ‘VOLUME’],
    start=‘2023-01-01’,
    end=‘2023-12-31’
)
print(prices.head())  # Inspect sample data

# Close session
ld.close_session()

Authentication

Four options. The first two are for the lseg.data library; the last two need no credentials of your own.

Platform session (works on every OS) — config file or environment variables, below. This is the only lseg.data session type available on Linux.

Desktop session — requires the Refinitiv Workspace desktop app running locally. Windows/macOS only; not an option on Linux.

Borrowed browser session — no credentials at all: scripts/workspace_cdp.py lifts the access token out of a signed-in Workspace Web tab. Use this when machine credentials are unavailable or expired. See references/workspace-web-cdp.md.

Codebook (hosted) — LSEG's own JupyterHub inside Workspace Web. Its kernels open a pre-authenticated DesktopSession named codebook on LSEG's servers, so it needs no local credentials and no local Workspace app, and it does not consume the one-session platform quota below. Same entitlements as the platform session, not broader. See references/codebook.md.

The rest of this section is about getting a platform session working, which is where all the sharp edges are.

On this setup: use the agenix secret

Credentials live in agenix as lseg-credentials, decrypted to $LSEG_CREDENTIALS_FILE (mode 400). It is a shell-sourceable file, so source it, do not cat it into a variable:

set -a; . "$LSEG_CREDENTIALS_FILE"; set +a   # exports LSEG_APP_KEY / LSEG_USERNAME / LSEG_PASSWORD

THE VARIABLE NAMES DO NOT MATCH THE LIBRARY'S. The secret exports LSEG_*; everything below documents RDP_*. You must map them at the call site. Reading this section and exporting RDP_APP_KEY from a file that defines LSEG_APP_KEY gets you an empty environment and a session that fails on first query.

Before 2026-07-27 these existed only as plaintext in mbp:~/projects/svb/.envrc, so anything running on another machine had no credentials at all. If a lookup comes back empty, check that host's rebuild is current before concluding the account is unentitled.

platform.Password DOES NOT EXIST

The config-file example below hides the programmatic form, and the obvious guess is wrong. In lseg-data 2.1.1 the class is GrantPassword:

import lseg.data as ld
from lseg.data.session import platform

s = platform.Definition(
        app_key=os.environ["LSEG_APP_KEY"],
        grant=platform.GrantPassword(username=os.environ["LSEG_USERNAME"],
                                     password=os.environ["LSEG_PASSWORD"]),
    ).get_session()
s.open()
ld.session.set_default(s)

platform exports exactly three names — ClientCredentials, Definition, GrantPassword. Check dir() before trusting a class name from the docs.

ONE concurrent platform session — pass signon_control=True

The machine ID allows a single concurrent platform session, and the library default is signon_control=False, which does not queue — it fails:

LDError: You authorised with signon_control=False. Session quota is reached.
If you want to open session close the previous opened.

Any earlier session that was not closed cleanly (a crashed script, another shell, a background job) holds the quota until it times out. Pass signon_control=True to take the signon over instead:

s = platform.Definition(
        app_key=os.environ["LSEG_APP_KEY"],
        grant=platform.GrantPassword(username=os.environ["LSEG_USERNAME"],
                                     password=os.environ["LSEG_PASSWORD"]),
        signon_control=True,          # <- take over rather than fail
    ).get_session()
s.open()
if str(s.open_state) != "OpenState.Opened":     # open() does not raise; see above
    raise RuntimeError(f"session failed: {s.open_state}")
ld.session.set_default(s)

Corollary: two local scripts cannot query at once. If you need a second concurrent path — an interactive query while a long batch runs — use Codebook, which authenticates as a separate DesktopSession and does not draw on this quota (see Refinitiv Codebook below).

open_session() DOES NOT RAISE ON FAILURE

With no config and no credentials it falls back to a desktop session, tries http://localhost:9000/api/handshake (LSEG Workspace running locally), logs the connection failure, and returns normally. The error only surfaces on the first query as ValueError: Session is not opened.

So open_session() returning is NOT evidence of a session. This is the same silent-failure shape as the Iron Law above, one layer earlier: verify by issuing a cheap query (TR.PriceClose on a liquid RIC) and inspecting the value.

Config file / environment variables (upstream documentation)

Configure LSEG authentication using either a config file or environment variables.

Config File Method

Create lseg-data.config.json:

{
  “sessions”: {
    “default”: “platform.ldp”,
    “platform”: {
      “ldp”: {
        “app-key”: “YOUR_APP_KEY”,
        “username”: “YOUR_MACHINE_ID”,
        “password”: “YOUR_PASSWORD”
      }
    }
  }
}

Environment Variables Method

Set the following environment variables for LSEG authentication:

# Configure LSEG credentials via environment variables
export RDP_USERNAME=”YOUR_MACHINE_ID”
export RDP_PASSWORD=”YOUR_PASSWORD”
export RDP_APP_KEY=”YOUR_APP_KEY”

Core APIs

APIUse CaseExample
ld.get_data()Point-in-time dataFundamentals, ESG scores
ld.get_history()Time seriesHistorical prices, OHLCV
ld.news.get_headlines()News headlinesCompany news, topic filtering
symbol_conversion.Definition()ID mappingRIC ↔ ISIN ↔ CUSIP

Key Field Prefixes

PrefixTypeExample
TR.Refinitiv fieldsTR.Revenue, TR.EPS
TR.MnAMergers & AcquisitionsTR.MnAAcquiror, TR.MnADealValue
TR.NIEquity/New Issues (IPOs)TR.NIIssuer, TR.NIOfferPrice
TR.JVJoint Ventures/AlliancesTR.JVDealName, TR.JVStatus
TR.SACTShareholder ActivismTR.SACTLeadDissident
TR.PPPoison PillsTR.PPPillAdoptionDate
TR.LNSyndicated LoansTR.LNTotalFacilityAmount
TR.PJFInfrastructure/Project FinanceTR.PJFProjectName
TR.PEInvestPrivate Equity/Venture CapitalTR.PEInvestRoundDate
TR.MuniMunicipal BondsTR.MuniIssuerName
CF_Composite (real-time)CF_LAST, CF_BID

RIC Symbology

SuffixExchangeExample
.ONASDAQAAPL.O
.NNYSEIBM.N
.LLondonVOD.L
.TTokyo7203.T

Rate Limits

EndpointLimit
get_data()10,000 data points/request
get_history()3,000 rows/request
Session500 requests/minute

Additional Resources

Reference Files

  • references/fundamentals.md - Financial statement fields, ratios, estimates
  • references/esg.md - ESG scores, pillars, controversies
  • references/symbology.md - RIC/ISIN/CUSIP conversion
  • references/short-interest.md - TR.ShortInterest: the only working field, the delisted-instrument coverage cliff, and the gap vs Compustat
  • references/guidance.md - Management guidance (TR.Guidance*), the content set behind LSEG Guidance Reports (GR): the ten fields that exist, SDate/EDate filtering the guided period rather than the announcement date, and datagrid row padding that inflates instance counts ~55%
  • references/pricing.md - Historical prices, real-time data
  • references/screening.md - Stock screening with Screener object
  • references/fscreen.md - Fund screening (ETFs, mutual funds) with FSCREEN app
  • references/fund-details.md - Fund details and characteristics
  • references/news.md - News headlines, pagination, query syntax
  • references/mna.md - Mergers & acquisitions deals (SDC Platinum, 2,683 fields)
  • references/equity-new-issues.md - IPOs, follow-ons, equity offerings (SDC Platinum, 1,708 fields)
  • references/joint-ventures.md - Joint ventures, strategic alliances (SDC Platinum, 301 fields)
  • references/corporate-governance.md - Shareholder activism, poison pills (SDC Platinum)
  • references/syndicated-loans.md - Syndicated loan deals (SDC Platinum)
  • references/infrastructure.md - Infrastructure/project finance deals (SDC Platinum)
  • references/private-equity.md - Private equity/venture capital investments (SDC Platinum)
  • references/people-and-boards.md - Officers/directors: the four TR.Officer* fields, no person id and no employment history, private companies reachable by PI, and why board data belongs in CIQ/BoardEx instead
  • references/municipal-bonds.md - Municipal bond issuances (SDC Platinum)
  • references/workspace-web-cdp.md - Driving the Workspace web client over CDP: session setup, token lifting, endpoint matrix, entitlement gotchas
  • references/codebook.md - Codebook (hosted JupyterHub): REST surface, and the kernel-execution blocker
  • references/api-discovery.md - Reverse-engineering APIs via CDP network monitoring
  • references/troubleshooting.md - Common issues and solutions
  • references/wrds-comparison.md - LSEG vs WRDS data mapping
  • ${CLAUDE_SKILL_DIR}/../crsp-lseg-splice/SKILL.md - using LSEG to extend a stale CRSP panel to T-1: CUSIP9→RIC linking, measured coverage, and the foreign-venue RIC trap (~4% of US CUSIPs resolve to a EUR-quoted cross-listing with no error)

Example Files

  • examples/historical_pricing.ipynb - Historical price retrieval
  • examples/fundamentals_query.py - Fundamental data patterns
  • examples/stock_screener.ipynb - Dynamic stock screening

Scripts

  • scripts/test_connection.py - Validate connectivity. No args tests the lseg.data platform session; --browser tests the CDP/Workspace-Web path.
  • scripts/dib_query.py - Query the Data Item Browser as a field dictionary: dib_query.py "segment revenue" prints real TR.* codes. Use it INSTEAD OF guessing field names; needs a signed-in Workspace tab with DIB open at the /web/ path. See references/api-discovery.md
  • scripts/guidance_pull.py - Pull TR.Guidance* instances with the timeout, padding drop and batching already handled: guidance_pull.py AAPL.O --start 2015-01-01 --end 2016-12-31 -o gr.csv
  • scripts/workspace_cdp.py - Drive Workspace Web over CDP: token, datagrid, history, symbology, search, sdc-screen, deal-data, fetch. Importable as a module or usable as a CLI.

Deal-level SDC work is two steps — sdc_deal_ids() resolves a SCREEN(U(IN(DEALS)) ...) universe to deal IDs, then deal_data() fetches field values for them as <id>@DEALID. See references/workspace-web-cdp.md.

Local Sample Repositories

LSEG API samples at ~/resources/lseg-samples/:

  • Example.RDPLibrary.Python/ - Core API examples
  • Examples.DataLibrary.Python.AdvancedUsecases/ - Advanced patterns
  • Article.DataLibrary.Python.Screener/ - Stock screening

Refinitiv Codebook

Hosted JupyterLab with a pre-authenticated, fully entitled refinitiv.data session:

  • URL: https://workspace.refinitiv.com/codebook/
  • Environment: JupyterHub 1.5.0dev, kernels python3 and python3_legacy
  • Session: auto-authenticated via Workspace cookies ({name='codebook'})

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
21
Forks
4
Last commit
Sep 2026

ahel review

  • K1binfo
    installs-packages
  • K1binfo
    installs-packages (in scripts/test_connection.py)
  • K1binfo
    installs-packages (in references/fund-details.md)

Automated review, not a security audit. Ruleset v1+k2.

Advanced
Catalog kind
skill
Gateway key
lseg-data
Source
github.com/edwinhu/workflows