fmp-py-sdk
fmp-py-sdk is the Python distribution for the Rust-backed fmp package, a typed client for the Financial Modeling Prep (FMP) data API.
The client exposes every libfmp endpoint method: 271 methods grouped into 30
domain namespaces on FmpClient. Each namespace is generated from a registry
that is validated against the real libfmp signatures, so the Python surface
cannot drift from the Rust one.
python -m pip install fmp-py-sdk
from fmp import FmpClient
client = FmpClient()
rows = client.quote.short("AAPL")
print(rows[0].symbol, rows[0].price)
FmpClient() reads the FMP_API_KEY environment variable when token is
omitted (unset, empty, or whitespace-only counts as absent); an explicit
token=... always wins, and auth_mode="none" ignores the variable. With
neither a token nor the variable, the default host raises FmpConfigError
naming FMP_API_KEY, while a custom base_url selects no auth.
FmpClient is synchronous: each call releases the Python GIL while the async
Rust transport waits, so threads keep running. A Python async facade is not
part of this release.
Requirements
- CPython 3.10 or newer (
abi3-py310: one wheel per platform covers every supported interpreter). - Rust is only needed to build from source; a built wheel is self-contained.
Namespaces
Endpoints live under one attribute per domain, and nested domains such as
statements group their sub-namespaces. Required arguments are positional,
optional ones are keyword-only, and every name is snake_case: the FMP wire
casing (sicCode, linkXlsx) never reaches Python.
from fmp import FmpClient
client = FmpClient()
quotes = client.quote.short("AAPL")
income = client.statements.income.statement("AAPL", period="annual", limit=5)
valuation = client.dcf.custom_discounted_cash_flow(
"AAPL", beta=1.2, tax_rate=0.21, long_term_growth_rate=4.0
)
| Namespace | Namespace | Namespace |
|---|---|---|
analyst |
esg |
market_hours |
bulk |
forex |
news |
calendar |
fundraising |
quote |
chart |
funds |
screener |
commitment_of_traders |
indexes |
search |
commodities |
insider_trading |
sec_filings |
company |
institutional_ownership |
statements |
congressional |
market |
technical_indicators |
crypto |
dcf |
tipranks |
directory |
economics |
transcripts |
statements nests as_reported, balance, cash_flow, growth, income,
metrics, ratios, reports, segmentation, and summaries.
Responses
- Typed rows: most methods return
list[Model], where each model is a generated, immutable, picklable class living in the domain package (for examplefmp.quote.QuoteShortorfmp.statements.income.IncomeStatement). Dates and naive timestamps aredatetime.dateanddatetime.datetime; RFC 3339 timestamps (for exampleTipRanksRatingSearchResult.date) staystrwith the exact wire text. Shared Rust response types map to one shared Python class rather than a copy per endpoint. - Dynamic rows: endpoints whose documented shape is open-ended return
list[dict[str, Any]]with the raw provider keys, for exampleclient.sec_filings.search_industry_classifications(symbol="AAPL"). A dynamic field inside a typed model (such asFinancialReportJson.sections) is exposed asAny. - Binary bodies:
client.statements.reports.xlsx("AAPL", 2022, "FY")returns a singlefmp.BinaryPayloadinstead of a list.datais the body asbytes, alongsidecontent_type,content_disposition, andbyte_len.
Validation and errors
Arguments are validated locally before any request: an empty ticker, a
malformed date, or a non-finite float raises FmpValidationError whose
message starts with the argument name, and nothing is sent. The exception
hierarchy lives in fmp.errors (every class is also exported from the
package root):
| Exception | Raised when |
|---|---|
FmpError |
base class; carries category, endpoint, status, body, body_truncated |
FmpConfigError |
the client cannot be built (missing key, bad URL, insecure auth) |
FmpValidationError |
an argument is rejected before the request |
FmpTransportError |
the request never produced a response |
FmpStatusError |
the provider answered with a non-success status |
FmpDecodeError |
the body could not be decoded into the documented shape |
from fmp import FmpClient
from fmp.errors import FmpStatusError
client = FmpClient()
try:
client.quote.short("AAPL")
except FmpStatusError as error:
print(error.status, error.endpoint, error.body)
Bodies attached to errors are redacted before they reach Python: an echoed
apikey query value shows as [REDACTED].
Custom router or proxy
import os
from fmp import FmpClient
client = FmpClient(
base_url=os.environ["FMP_PROXY_BASE_URL"],
path_prefix="router/stable",
auth_mode="custom_header",
auth_name="X-Proxy-Token",
auth_prefix="Bearer ",
token=os.environ["FMP_PROXY_TOKEN"],
headers={"X-Tenant": os.environ["FMP_TENANT"]},
)
rows = client.quote.short("AAPL")
Available auth modes are none, fmp_header, fmp_query, bearer, custom_header, and custom_query. auth_mode="none" supports credential-free local or trusted routers. Redirect following is either disabled or same-origin only.
Typing
The package ships a py.typed marker, and every native module has a .pyi
stub generated by pyo3-stub-gen from the Rust signatures, so pyright and
mypy see the exact method names, keyword-only arguments, and return types.
The public modules follow a conventional HTTP SDK layout: FmpClient lives in
fmp.client, the exception hierarchy in fmp.errors, and the models and
namespace classes in the domain packages such as fmp.quote and
fmp.statements.income. Transport, configuration, and runtime internals are
intentionally not exposed as Python modules.
Secret URLs
FinancialReportDate.link_json / link_xlsx (the fmp.statements.reports model
listing available financial reports) are download links that embed your API key. The Python model treats them the
way FmpClient treats danger_allow_insecure_authentication: the unsafe path
exists, but it is conspicuous.
- The links are not attributes. Read one with
row.expose_secret_url_json()orrow.expose_secret_url_xlsx()and treat the value as a credential. repr(row)andstr(row)printlink_json=[REDACTED URL], so a strayprintor log line never leaks the key.- For local debugging only,
fmp.set_reveal_secret_urls(True)reveals the URLs inrepr()process-wide (fmp.reveal_secret_urls()reads the flag back). SettingFMP_REVEAL_SECRET_URLS=1(alsotrue/yes, case-insensitive) before the first use turns it on at start-up. - Pickles of the model contain the real URLs:
pickle.dumpsmust round-trip the value, so store them as carefully as the key itself.
Regenerating the response models
The pyo3 response models under crates/fmp-py/src/models/ are generated
from the libfmp response structs by the gen_models binary in the sibling
crates/fmp-py-gen crate. Run it from the repository root:
cargo run -p fmp-py-gen --bin gen_models
The generator only depends on syn, not on fmp-py, so it still builds and
runs when fmp-py fails to compile against a changed libfmp. Regeneration
must be idempotent: git status crates/fmp-py/src/models is clean after a
second run.
Validating the endpoint registry
The Python namespaces are described by one TOML file per domain under
crates/fmp-py-gen/registry/ (quote.toml for client.quote). Each entry
names the Python method, the libfmp Client method, its query type, the
constructor arguments and optional with_* setters with their arg kinds, and
the generated response model. Validate the registry against the real libfmp
signatures and the generated models from the repository root:
cargo run -p fmp-py-gen --bin registry_check
The check parses crates/libfmp/src/endpoints/** with syn, so an unknown
method, a mismatched query type, an unknown arg kind, a setter that does not
exist, or a missing model file fails with the file and entry named. Query
types emitted by macro_rules! are recovered by expanding the macro; the
report says whether each entry was verified directly, through a macro, or
trusted because its constructor could not be seen, and ends with the total
(registry ok: 271 verified, 0 trusted). Pass a directory argument to check
a different registry tree.
Regenerating the endpoint namespaces
The namespace classes under crates/fmp-py/src/namespaces/ (QuoteNamespace
for client.quote) are generated from the same registry by the
gen_namespaces binary, which validates the registry first and emits nothing
on any error. Run it from the repository root:
cargo run -p fmp-py-gen --bin gen_namespaces
Flat domains become namespaces/<domain>.rs; a nested path such as
statements.income becomes namespaces/statements/mod.rs (the parent with a
getter per sub-namespace) plus namespaces/statements/income.rs. Required
arguments are positional, optional ones keyword-only, and an argument named
after a Python keyword (from) is spelled with a trailing underscore
(from_) on the Python side while the libfmp setter keeps its name.
Entries marked binary = true (the endpoints whose libfmp method returns
BinaryResponse, such as the XLSX financial report download) return a single
BinaryPayload instead of a list of models; entries marked
response = "dynamic" return list[dict[str, Any]]. BinaryPayload is
hand-written in src/binary.rs and exported from the package root as
fmp.BinaryPayload.
The same run emits the wiring that binds the generated code into the
extension, so adding a domain never edits lib.rs or client.rs:
| File | Contents |
|---|---|
src/namespaces/mod.rs |
one mod declaration per domain |
src/registration.rs |
register_namespaces: every fmp._native.<path> submodule with its models (read back from src/models/** with syn) and namespace classes, published in sys.modules |
src/client_namespaces.rs |
one FmpClient getter per domain, a second #[pymethods] block (pyo3's multiple-pymethods feature) |
src/facade_domains.rs |
a wildcard reexport_module_members! per fmp._native.<path>, which is what writes the public python/fmp/<path>/__init__.py packages |
The hand-written src/facade.rs keeps only the top-level fmp surface.
Regenerating the Go SDK
The Go module at sdk/go (ADR 0030) is generated from the same registry, the
wire contract read from crates/libfmp/src/endpoints/**, and the response
structs, by the gen_go binary. The three generators, from the repository
root:
cargo run -p fmp-py-gen --bin gen_models # fmp-py response models
cargo run -p fmp-py-gen --bin gen_namespaces # fmp-py endpoint namespaces
cargo run -p fmp-py-gen --bin gen_go # sdk/go, every generated domain
gen_go --domain <name> (repeatable) generates the named domains; with no
argument it regenerates every domain that already carries the generated
header; --all regenerates all 30. It writes sdk/go/<domain>_models.go,
sdk/go/<domain>.go, and the shared queries.go and namespaces.go, all
through gofmt, so a second run leaves git status clean; the
scripts/check_go_sdk.sh gate proves it. A Rust type or argument kind the
generator does not know fails the run naming the struct and field (or the
query.go helper to add); nothing is emitted in that case.
Regenerating the stubs and public packages
The .pyi stubs under python/fmp/_native/ and the public
python/fmp/<path>/__init__.py packages are written by pyo3-stub-gen
through the stub_gen binary, from the repository root:
cargo run -p fmp-py --bin stub_gen
stub_gen post-processes each __init__.pyi (absolute imports, no
# ruff: noqa header, ruff format --isolated --line-length 88 in .py
mode) so a run on an unchanged tree leaves git status clean. It needs
ruff at the version pinned in this crate's .pre-commit-config.yaml and as
RUFF_VERSION in src/bin/stub_gen.rs (currently 0.15.12, the two must
agree): either that exact ruff on PATH or uvx, which fetches it. Run it
after gen_models or gen_namespaces, and commit the result.
This project is available under the MIT License.
Release files for fmp-py-sdk 0.11.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 | |
|---|---|---|---|
| fmp_py_sdk-0.11.0.tar.gz | 445.8 kB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fmp_py_sdk-0.11.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| fmp_py_sdk-0.11.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| fmp_py_sdk-0.11.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 23.0 MB
Release files / fmp_py_sdk-0.11.0.tar.gz
| Download URL | fmp_py_sdk-0.11.0.tar.gz |
|---|---|
| Size | 445.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4a1a6654bb956005186de73dbd87086432ef52d3ed0506e31b03cdee66a34009
|
|
BLAKE2b-256 checksum How to use checksums |
d0965d27d8d68a132a88e89feebdc1e0671d000333abe46e81669bccb0e8751b
|
| 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 24, 2026.
Transparency logRelease files / fmp_py_sdk-0.11.0-cp310-abi3-win_amd64.whl
| Download URL | fmp_py_sdk-0.11.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 8.0 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
09e2a2b7b0612500ba1463f3e707a7cb9fa71b84edeb3ff7b5d12f3596cf9ecc
|
|
BLAKE2b-256 checksum How to use checksums |
3870dac83a81b8d24a0a02d08b58f8d6da2635f7b3d85cb366063776d25019b7
|
| 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 24, 2026.
Transparency logRelease files / fmp_py_sdk-0.11.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | fmp_py_sdk-0.11.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 7.7 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
a35720ce9a20f24ed73f8983f92ab11cb420ea7dcb420c25ac084049d4a5cb09
|
|
BLAKE2b-256 checksum How to use checksums |
6c9b74bf9644e30d4ff12b84fec8bc92158f3c4a5b036cb6b595f88f710779a9
|
| 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 24, 2026.
Transparency logRelease files / fmp_py_sdk-0.11.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | fmp_py_sdk-0.11.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 6.9 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
a6f3a0fd8330815bfd23877f1995857bc6cd2620d099af4df919ae12a200516a
|
|
BLAKE2b-256 checksum How to use checksums |
84b5f43b390bd6180c290cb1bb22c5df2d375feeb1825fbfb86060c873458151
|
| 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 24, 2026.
Transparency log