thaler
The Thaler API from Python: financial data from SEC filings, with the source filing for every value.
pip install thaler
import thaler
client = thaler.Thaler() # reads THALER_API_KEY; or Thaler(api_key="thaler_…")
rows = client.metrics("AAPL", period="annual", keys=["revenue", "net_income"])
for row in rows.data:
source = row.read_from.accession_number if row.read_from else None
print(row.end_date, row.metric_key, row.value, source)
# 2025-09-27 revenue 416161000000 0000320193-25-000079
Every figure is a decimal.Decimal, every day a datetime.date, and
every answer a Response with the request ID and the account’s limits
beside its data. Create a key at
thaler.sh/developers/keys.
What the client does
At most four requests are in flight at once. When the minute’s sixty are
spent, the next request waits for the reset. A 429 is retried after its
Retry-After; one longer than a minute, the month’s limit, raises
RateLimitError at once.
A 500, 502, 503 or 504, or a connection that failed, is retried twice
after a growing pause; those answers don’t count against the month. A
400, 401 or 404 is raised as it stands, and a Screener clause the API
would refuse is a ValueError before anything is sent.
Figures are Decimal (a float loses digits past the fifteenth), days
are date, instants are datetime, and each model is a frozen
dataclass with None for what is absent or null. An answer that is not
as documented raises DecodeError, naming the field and the request ID.
iter_screen, iter_holders, iter_holder_positions and
iter_insider_filings yield rows across pages.
NotFoundError, AuthenticationError, BadRequestError,
RateLimitError and ServerError are each an APIError with the
problem’s code, detail and request_id.
The client
client = thaler.Thaler(
api_key=None, # or THALER_API_KEY
timeout=30.0, # seconds to wait for an answer
max_retries=2, # tries after the first, on 429, 500, 502, 503, 504 or a lost connection
max_concurrent=4, # requests in flight; the API allows four
max_retry_after=60.0, # the longest Retry-After waited for
)
with thaler.Thaler() as client: closes the connections on the way out.
thaler.AsyncThaler is the same client for asyncio: every method
awaited, every iter_* an async iterator.
Every call
| Call | Answers with |
|---|---|
search_securities(query) |
list[SecuritySearchHit] |
profile(ticker) |
SecurityProfile |
metrics(ticker, period=, keys=, collapse=, limit=, as_of=) |
list[MetricValue] |
metric_catalog() |
list[MetricCatalogEntry] |
metric_lineage(ticker, metric_key, metric_value_id=, limit=, as_of=) |
list[MetricLineage] |
metric_revisions(ticker, metric_key, metric_value_id=, fiscal_year=, fiscal_period=, limit=) |
list[MetricRevision] |
raw_concepts(ticker, limit=) |
list[RawConcept] |
filings(ticker, forms=, items=, limit=) |
list[Filing] |
filings_day(date=) |
FilingsDay |
filing(accession) |
FilingSource |
insider_activity(ticker=None, tickers=, since=, kind=, limit=, offset=) |
InsiderActivityPage |
search_holders(query=, limit=) |
list[HolderHit] |
holder(cik, limit=, offset=) |
Holder |
holders(ticker, limit=, offset=) |
SecurityHolders |
segments(ticker, period=) |
Segments |
prices(ticker, range=, from_=, to=) |
Prices |
screen(where=, sort=, dir=, limit=, offset=, columns=) |
list[ScreenRow] |
release() |
Release |
Each returns a Response: .data as above, .meta (the route, the
parameters as the API read them, the Screener’s counts, the release),
.request_id, .etag, .rate_limit and .headers.
The Screener
from thaler import where
big_and_profitable = client.screen(
where=[where("revenue", ">=", 10_000_000_000), where("net_margin", ">", 0.2)],
sort="revenue",
dir="desc",
columns=["revenue", "net_margin", "market_cap"],
)
for row in big_and_profitable.data:
print(row.ticker, row.revenue, row.net_margin)
print(big_and_profitable.meta.total, "matches")
for row in client.iter_screen(where=[where("fcf_margin", ">=", 0.15)], limit=1000):
...
where writes a clause as the API takes it (revenue:gte:10000000000,
never scientific notation); a clause written by hand works as well.
Point-in-time reads
then = client.metrics("KHC", period="annual", keys=["net_income"], as_of="2019-03-01")
now = client.metrics("KHC", period="annual", keys=["net_income"])
Each value comes from the latest filing on or before the day, so a backtest only sees what was public at the time. See the guide on point-in-time data.
Errors
try:
client.profile("ZZZZ")
except thaler.NotFoundError as error:
print(error.code, error.detail, error.request_id)
except thaler.RateLimitError as error:
print("wait", error.retry_after, "seconds;", error.violated_policies)
except thaler.APIError as error:
print(error.status, error.code)
except thaler.TransportError:
print("no answer")
Prices
Prices include IEX’s last sale for each trading day. Data provided for free by IEX. By accessing or using IEX Historical Data, you agree to the IEX Historical Data Terms of Use.
Versions
The SDK is 0.x while the API is in beta. thaler.API_VERSION names
the API document a release follows. Changes are in CHANGELOG.md,
shipped with the package.
Metadata
Release files for thaler 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| thaler-0.1.0.tar.gz | 33.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| thaler-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 70.7 kB
Release files / thaler-0.1.0.tar.gz
| Download URL | thaler-0.1.0.tar.gz |
|---|---|
| Size | 33.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
19677a43fa42f213acc507d7d22ea08d634df6c698adce0afbbc5f3b77500482
|
|
BLAKE2b-256 checksum How to use checksums |
5007393aa3844ddb0c1a443b7d7c379afa419e5a79893aed6cae4d187d5f9dfd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency logRelease files / thaler-0.1.0-py3-none-any.whl
| Download URL | thaler-0.1.0-py3-none-any.whl |
|---|---|
| Size | 37.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0b9283857c581593b5b42c319901af6b47dbb64a3afb353bae233b613fa851ff
|
|
BLAKE2b-256 checksum How to use checksums |
5c942654c45997f06598e63edba316fcdb4d7ed2bfee9a90eb9f9375920cceb0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency log