# Get European company financial statements in Python

> A runnable Python example using a public dated sample or an authenticated REST request. Preserve nulls, currency, sources and annual reporting periods.

By European Financial Data. Reviewed 17 October 2026.

Canonical article: https://eurofindata.com/research/european-financial-data-python

## The short answer

Use the public ASML sample to test the calculation without an account, then switch deliberately to the authenticated income-statement endpoint for current available records. These are company financial statements, not live stock prices. MCP remains the primary option for interactive agent research.

## Start with a public financial-statement sample

This example uses the same dated ASML EU-IFRS records as the [margin walkthrough](https://eurofindata.com/research/asml-operating-margin). It computes an operating margin, not a stock return or live quote. Python’s standard library is sufficient.

Download the [sample JSON](https://eurofindata.com/research/european-financial-data-python/sample.json) and [Python script](https://eurofindata.com/research/european-financial-data-python/example.py). The public sample requires no account and consumes no account allowance. Its amounts are in full EUR units; the display table elsewhere rounds to billions.

Run the downloaded files

```text
python3 european-financial-data.py --input european-financial-data-python-sample.json
```

The output identifies a dated sample, retains reporting periods and source labels, and calculates approximately 36.9% for 2025. Missing or zero revenue produces an unavailable margin, not a division error or invented zero.

## The complete calculation

Python 3 · standard library · same source as the download

```text
"""Read a dated public sample or explicitly request available live statements.

No third-party Python packages are required. Store real account keys privately.
"""
import argparse
import json
import os
from decimal import Decimal
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

parser = argparse.ArgumentParser()
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("--input", type=Path, help="Downloaded public sample JSON")
mode.add_argument("--live", action="store_true", help="Use account allowance")
args = parser.parse_args()

if args.live:
    key = os.environ.get("EUROFINDATA_API_KEY")
    if not key:
        raise SystemExit("Set EUROFINDATA_API_KEY privately before using --live.")
    url = ("https://api.eurofindata.com/v1/financials/income-statements"
           "?ticker=ASML&period=annual&limit=5")
    request = Request(url, headers={"Authorization": "Bearer " + key})
    try:
        with urlopen(request, timeout=30) as response:
            payload = json.load(response)
    except HTTPError as error:
        raise SystemExit(f"Data request failed: HTTP {error.code}. Check access and allowance.")
    except URLError:
        raise SystemExit("Data request failed. Check connectivity and try later.")
    rows = payload.get("income_statements", [])
    print("LIVE REQUEST: verify source freshness, accounting basis and report versions.")
else:
    payload = json.loads(args.input.read_text(encoding="utf-8"))
    if payload.get("is_live") is not False or payload.get("issuer") != "ASML":
        raise SystemExit("Expected the dated ASML public sample.")
    rows = payload.get("records", [])
    print("DATED SAMPLE:", payload["sample_as_of"], payload["accounting_basis"])

if not isinstance(rows, list) or not rows:
    raise SystemExit("No statements returned. An empty result is not zero revenue.")

seen = set()
for row in sorted(rows, key=lambda value: value.get("report_period", value.get("period_end", ""))):
    period = row.get("report_period", row.get("period_end"))
    currency = row.get("currency")
    identity = (period, currency)
    if not period or not currency or identity in seen:
        raise SystemExit("Missing period/currency or duplicate period. Select a report version first.")
    seen.add(identity)
    revenue, income = row.get("revenue"), row.get("operating_income")
    margin = None
    if revenue is not None and income is not None and Decimal(str(revenue)) != 0:
        margin = Decimal(str(income)) / Decimal(str(revenue)) * 100
    display = "not available" if margin is None else f"{margin:.1f}%"
    print(period, currency, "revenue=", revenue, "operating_income=", income,
          "margin=", display, "source=", row.get("source", "not supplied"))

```

The script refuses duplicate period/currency records so you can choose a report version deliberately. That is a basic guard, not a complete point-in-time or accounting-comparability check.

## Switch deliberately to an authenticated request

For current available records, create an account key and set `EUROFINDATA_API_KEY` privately in your environment. Run the script with `--live`. This makes an authenticated request to the annual income-statement endpoint and uses the account’s shared allowance.

Live mode · an account key must already be configured privately

```text
python3 european-financial-data.py --live
```

The REST response uses `income_statements` and `report_period`; the curated sample uses `records` and `period_end`. The script handles both intentionally. A new live response can have different periods, values or gaps from the static sample.

Read the [endpoint reference](https://eurofindata.com/docs/api-reference#financial-statements) and [freshness guide](https://eurofindata.com/research/financial-data-freshness). A returned statement still needs its source and accounting-basis checks. The code has been tested with the public sample and fixture responses; this page does not claim a fresh customer-authenticated live run.

## For an interactive question, use MCP

A script is useful for an explicit calculation or application. If you want to ask follow-up questions in a compatible agent, start with [MCP setup](https://eurofindata.com/docs/mcp) and let the client discover the available tools. Both interfaces share account usage and underlying coverage.

The same rule applies either way: preserve periods, units, currency and source, and distinguish a missing observation from a zero value.

## Reproduce the sample

Dated sample: 2026-10-04. Not live data. Amounts are in full currency units. EU-IFRS/ESEF; not the US GAAP series. CSV empty numeric cells mean missing values, not zero.

- [Sample JSON](https://eurofindata.com/research/european-financial-data-python/sample.json)
- [Sample CSV](https://eurofindata.com/research/european-financial-data-python/sample.csv)

## Sources and further reading

- [ASML 2025 EU-IFRS annual report, consolidated profit or loss, page 270 (PDF)](https://ourbrand.asml.com/m/6ea363f69344ebd4/original/asml-2025-annual-report-based-on-ifrs.pdf#page=270)
- [ASML FY2025 machine-readable source](https://filings.xbrl.org/724500Y6DUVHQD6OXN27/2025-12-31/ESEF/NL/0/asml-2025-12-31-1-en.json)
- [REST statement endpoint and response shape](https://eurofindata.com/docs/api-reference#financial-statements)
- [Our source and freshness documentation](https://eurofindata.com/docs/provenance)

## Put your next company question to work.

Free testing · No payment required · 300 successful data requests per account per UTC day

- [Create a free account](https://eurofindata.com/sign-up)
- [Connect your agent](https://eurofindata.com/agents.md)

## Keep exploring

- [Analyze ASML’s operating margin with an AI agent](https://eurofindata.com/research/asml-operating-margin)
- [MCP vs REST for financial research](https://eurofindata.com/research/mcp-vs-rest-financial-data)
- [How to check whether financial data is actually current](https://eurofindata.com/research/financial-data-freshness)
