---
name: simfin-ingestion
description: "How to get SimFin data into a pandas DataFrame and Parquet: access, the request, the format, limits, quirks and terms."
---

# Load SimFin income statements into pandas and Parquet for personal research

Source entry: https://signal-lab-jet.vercel.app/datasets/simfin
Provider: https://www.simfin.com
Checked against the provider's documentation: 2026-10-29

## Access
- A free account and API key are required. Put it in SIMFIN_API_KEY.
- Free and Basic data is licensed for non-commercial personal research.

## Request
```bash
curl -H 'Authorization: '$SIMFIN_API_KEY 'https://backend.simfin.com/api/v3/companies/statements/compact?ticker=AAPL&statements=PL'
```

## Format
JSON list with one object per company (ticker, name, currency and other fields). Each company has a statements list; each statement has a statement code, a columns list and data rows (arrays in column order). SimFin's API reference does not document the response body; this shape follows SimFin's v3 compact format as parsed by the open-source simfinapi R client.

## Load into pandas and Parquet
Needs Python 3 with requests, pandas and pyarrow.
```python
"""SimFin compact profit-and-loss statements -> pandas -> Parquet. Personal research."""
import os
import requests
import pandas as pd

headers = {"Authorization": os.environ["SIMFIN_API_KEY"]}
params = {"ticker": "AAPL", "statements": "PL"}
r = requests.get("https://backend.simfin.com/api/v3/companies/statements/compact",
                 headers=headers, params=params, timeout=30)
r.raise_for_status()
frames = []
for company in r.json():  # one object per company
    for stmt in company.get("statements") or []:
        part = pd.DataFrame(stmt["data"], columns=stmt["columns"])  # rows are arrays in column order
        part.insert(0, "statement", stmt.get("statement"))
        part.insert(0, "ticker", company.get("ticker"))
        frames.append(part)
df = pd.concat(frames, ignore_index=True)
df.to_parquet("simfin_aapl_income_statements.parquet", index=False)
print(len(df), "statement rows;", len(df.columns), "columns")
```
Needs a personal API key; not run.

## Limits
- Free: the pricing comparison table lists 7 years of Fundamentals API data history, 5 years (delayed) for bulk CSV download and a monthly budget of 500 high-speed credits. The Free plan card on the same page says 5 years of fundamentals history, so the page conflicts; check the history your key returns.
- Free API rate: two calls per second.

## Quirks
- Compact rows are arrays; map them onto each statement's columns list, as the snippet does.
- Credits and ordinary request limits are separate concepts.
- Ticker coverage is concentrated in US stocks on the free plan.

## Terms and attribution
- Free and Basic data is for personal research, not commercial use.
- Sharing and redistribution are not allowed under that license.
- Commercial use requires the applicable commercial license.
- Terms page: https://www.simfin.com/en/commercial-license/

## Sources
- https://www.simfin.com/en/prices/: "5 Years Fundamentals History"
- https://www.simfin.com/en/prices/: "FUNDAMENTALS API Data History 7 Years"
- https://www.simfin.com/en/commercial-license/: "§1 FREE / BASIC: “Non-commercial use”"
- https://www.simfin.com/en/commercial-license/: "personal research"
- https://raw.githubusercontent.com/cran/simfinapi/master/R/sfa_load_statement.R: "lapply(stmt[["data"]], \(data_array)"

Information for educational purposes only. Free tiers, limits and licenses belong to the providers and may change; check the provider's own terms before using the data.
