Accepted for the P1 shared-contract foundation.
artifacts/documentations/outer.md documents multiple wire representations for
the same conceptual value. Examples include fiscal years as integers and
strings, financial values as numbers and numeric strings, percentages as
numbers, numeric strings, and percent-suffixed strings, and several unrelated
date and boolean formats. Coercing these values into one permissive type would
lose provider information and make later endpoint models unreliable.
The Rust core exposes narrow query enums and lossless response codecs. Similar wire forms remain separate when their endpoint semantics differ: fiscal periods are not retrieval frequencies, numeric quarters are not fiscal-period strings, and RFC 3339 timestamps are not accepted by date or naive-datetime fields. Endpoint-specific availability, add-on requirements, conditional plan requirements, delay declarations, and bounds are attached as additive metadata. Named add-on entitlement and conditional plan entitlement compose: for example, TipRanks access can require the TipRanks add-on generally while only history older than three years requires the Enterprise plan. There is no global limit, page, or nonzero rule.
The Python binding applies this conversion policy to public endpoint models
(the mapping lives in crates/fmp-py-gen/src/bin/gen_models/classify.rs and
crates/fmp-py/src/convert.rs):
- JSON integers remain Python
int; values are never routed throughf64. Integer literals beyondu64stay exact through the dynamic path. - Fractional JSON numbers become Python
float. - Numeric strings and percent-suffixed strings remain Python
strand are not scaled. - A fiscal year matches its wire representation per field:
FiscalYearStringfields becomestrandCalendarYearfields becomeint. TheFiscalYearunion is not used by any public model, and the generator has no mapping for it; a model that needs it would also need generator work. - Native JSON booleans become Python
bool;Y/N,Yes/No, lowercasetrue/false, and title-caseTrue/Falseforms remain validated strings unless a field's public model deliberately exposes its typed flag enum. YYYY-MM-DDvalues becomedatetime.date; naiveYYYY-MM-DD HH:MM:SSvalues become naivedatetime.datetime; RFC 3339IsoTimestampvalues remainstrholding the exact wire text, so the offset spelling and millisecond precision are preserved rather than normalised into a timezone-awaredatetime.datetime. Date-or-datetime fields remainstr, preserving which of the two documented forms was received. Date-or-year fields (ADR 0034) becomedatetime.date | int. Empty and null optional dates becomeNone. Opaque human or partial date text remainsstr.- Dynamic JSON recursively becomes native Python dictionaries, lists, strings,
integers, floats, booleans, and
None. - Binary response bytes become Python
bytesonfmp.BinaryPayload, which carries the validatedcontent_typeand optionalcontent_dispositionalongsidedata. - Query enums are accepted as validated Python strings while Rust retains the
exact enum vocabulary. Closed enums match case-insensitively and
quarterlyis an accepted alias, but the encoded wire value is always the documented spelling. The open economic-indicator name matches the documented spellings exactly and preserves any other non-empty value asOther.
Amendment (2026-09-21, audit of issue #11): the introduction and the integer,
fiscal-year, timestamp, binary, and query-enum bullets above were revised to
describe the shipped binding. The
original text promised timezone-aware datetimes for RFC 3339 values and an
int | str fiscal-year union; neither shipped, and the domain ADRs (0011,
0018, 0029) together with the binding tests assert the representation-preserving
str behaviour instead.
Dynamic object-root payloads use DynamicObject. It preserves arbitrary member
names and recursively lossless JSON data, including arbitrary-precision integer
tokens, but does not treat semantically insignificant object-member order as
data. Duplicate names follow the JSON map model. Consequently, this foundation
does not enable an order-preservation dependency.
Binary downloads use the header-aware BinaryResponse value. BinaryBody
remains a source-compatible alias. Both names provide as_bytes() and
into_bytes(); BinaryResponse additionally provides the exact validated
content_type() and an optional textual content_disposition(). Its Debug
implementation includes only the byte length, validated base media type, and
whether a disposition is present. It never prints body bytes, disposition
contents, or content-type parameters.
Each endpoint owns its accepted MIME allow-list because the provider source does
not establish one global binary MIME policy. The client validates Content-Type
against that endpoint contract before constructing BinaryResponse; optional
Content-Disposition is retained only when it is valid header text. Since #411
a body with the magic bytes of an expected format is also accepted under a
mismatched content type (ADR 0008).
PartialEq and Eq compare bytes, exact content type, and optional content
disposition. Metadata is part of binary response identity rather than incidental
transport state. The compatibility alias preserves source compatibility, while
the equality semantics intentionally reflect the enriched response value.