jintel
Python client for the Jintel intelligence API. Typed queries, Pydantic-validated responses, an optional response cache, and a dynamic query builder that threads per-field filters into the underlying GraphQL.
Mirrors @yojinhq/jintel-client section-for-section.
Install
pip install jintel
Requires Python 3.10+.
Quick start
import os
from jintel import JintelClient
with JintelClient(api_key=os.environ["JINTEL_API_KEY"]) as jintel:
quotes = jintel.quotes(["AAPL", "MSFT", "BTC"])
aapl = jintel.enrich_entity("AAPL", fields=["market", "news", "analyst"])
Async twin — same surface, await-able:
import asyncio, os
from jintel import AsyncJintelClient
async def main() -> None:
async with AsyncJintelClient(api_key=os.environ["JINTEL_API_KEY"]) as jintel:
quotes = await jintel.quotes(["AAPL"])
asyncio.run(main())
All public methods return JintelResult[T] — a discriminated union Ok[T] | Err — so errors never throw. Pattern-match instead of try/except:
from jintel import Ok, Err
result = jintel.quotes(["AAPL"])
match result:
case Ok(data=quotes):
for q in quotes:
if q is None:
continue # PIT UNSUPPORTED slot
print(q.ticker, q.price)
case Err(error=msg):
log.warning("quotes failed: %s", msg)
For arbitrary GraphQL, use jintel.request(query, variables) (raises) or jintel.raw_request(query, variables) (returns the full envelope including extensions).
Filtering array sub-graphs
Most array fields accept an optional dedicated filter. The generic ArrayFilterInput covers date range + limit + sort; many sub-graphs have domain-specific inputs with extra dimensions.
from jintel import EnrichOptions
from jintel.filters import (
ArrayFilterInput, NewsFilterInput, ExecutivesFilterInput,
InsiderTradeFilterInput, EarningsFilterInput, SegmentRevenueFilterInput,
TopHoldersFilterInput, FinancialStatementFilterInput,
SanctionsFilterInput, CampaignFinanceFilterInput, FilingsFilterInput,
RiskSignalFilterInput, OptionsChainFilterInput, FuturesCurveFilterInput,
SortDirection,
)
from jintel.types import (
AcquisitionDirection, ExecutiveSort, FilingType, OptionsChainSort,
OptionType, RiskSignalType, Severity,
)
# Generic ArrayFilterInput — research, predictions, discussions, social,
# institutionalHoldings, earningsPressReleases, periodicFilings,
# market.history/keyEvents/shortInterest, economics.
jintel.enrich_entity("AAPL", fields=["research", "market"], options=EnrichOptions(
filter=ArrayFilterInput(since="2025-01-01", limit=10, sort=SortDirection.DESC),
))
# NewsFilterInput — filter by source and sentiment score
jintel.enrich_entity("AAPL", fields=["news"], options=EnrichOptions(
news_filter=NewsFilterInput(min_sentiment=0, limit=10),
))
# ExecutivesFilterInput — top-paid officers only
jintel.enrich_entity("AAPL", fields=["executives"], options=EnrichOptions(
executives_filter=ExecutivesFilterInput(
min_pay=1_000_000, sort_by=ExecutiveSort.PAY_DESC, limit=5
),
))
# InsiderTradeFilterInput — directors only, acquisitions >= $100k
jintel.enrich_entity("AAPL", fields=["insiderTrades"], options=EnrichOptions(
insider_trades_filter=InsiderTradeFilterInput(
is_director=True,
acquired_disposed=AcquisitionDirection.ACQUIRED,
min_value=100_000,
),
))
# EarningsFilterInput — reported beats >= 5%
jintel.enrich_entity("AAPL", fields=["earnings"], options=EnrichOptions(
earnings_filter=EarningsFilterInput(only_reported=True, min_surprise_percent=5),
))
# SegmentRevenueFilterInput — product breakdown >= $1B
jintel.enrich_entity("AAPL", fields=["segmentedRevenue"], options=EnrichOptions(
segmented_revenue_filter=SegmentRevenueFilterInput(
dimensions=["PRODUCT"], min_value=1_000_000_000
),
))
# TopHoldersFilterInput — paginated top-holder lookup
jintel.enrich_entity("AAPL", fields=["topHolders"], options=EnrichOptions(
top_holders_filter=TopHoldersFilterInput(limit=25, offset=0, min_value=50_000),
))
# FinancialStatementFilterInput — annual only
jintel.enrich_entity("AAPL", fields=["financials"], options=EnrichOptions(
financial_statements_filter=FinancialStatementFilterInput(
period_types=["12M"], limit=5
),
))
# SanctionsFilterInput + CampaignFinanceFilterInput — on regulatory
jintel.enrich_entity("Gazprom", fields=["regulatory"], options=EnrichOptions(
sanctions_filter=SanctionsFilterInput(min_score=80, programs=["SDGT"]),
campaign_finance_filter=CampaignFinanceFilterInput(cycle=2024, party="DEM"),
))
# FilingsFilterInput — narrow SEC filings by form type
jintel.enrich_entity("AAPL", fields=["regulatory"], options=EnrichOptions(
filings_filter=FilingsFilterInput(
types=[FilingType.FILING_10K, FilingType.FILING_10Q], limit=5
),
))
# RiskSignalFilterInput — drop low-severity noise
jintel.enrich_entity("Gazprom", fields=["risk"], options=EnrichOptions(
risk_signal_filter=RiskSignalFilterInput(severities=[Severity.HIGH, Severity.CRITICAL]),
))
# OptionsChainFilterInput — chains can exceed 5 000 rows; filter aggressively
jintel.enrich_entity("BTC", fields=["derivatives"], options=EnrichOptions(
options_filter=OptionsChainFilterInput(
option_type=OptionType.CALL,
strike_min=60_000, strike_max=80_000,
min_open_interest=100,
sort=OptionsChainSort.VOLUME_DESC,
limit=25,
),
))
# FuturesCurveFilterInput — defaults to ASC (nearest contract first)
jintel.enrich_entity("BTC", fields=["derivatives"], options=EnrichOptions(
futures_filter=FuturesCurveFilterInput(limit=10),
))
Each filter option applies to one sub-graph only, so you can mix them in a single request. The generic filter no longer applies to fields that migrated to domain-specific inputs (news, executives, insiderTrades, earnings, segmentedRevenue, topHolders, financials.*, regulatory.sanctions, regulatory.campaignFinance) — use the dedicated option for those, or the client raises JintelValidationError at call time.
Top-level queries
Economics and short-interest queries accept the generic ArrayFilterInput. sanctions_screen and campaign_finance accept their domain-specific filters:
from jintel.types import GdpType
from jintel.filters import ArrayFilterInput, SanctionsFilterInput, CampaignFinanceFilterInput
jintel.gdp("USA", type=GdpType.REAL,
filter=ArrayFilterInput(since="2010-01-01", limit=20))
jintel.inflation("USA", filter=ArrayFilterInput(since="2020-01-01"))
jintel.short_interest("GME", filter=ArrayFilterInput(limit=5))
# Root sanctions screen — filter by score, list, or program
jintel.sanctions_screen(
"Gazprom", country="RU",
filter=SanctionsFilterInput(min_score=80, list_names=["SDN"]),
)
# Root campaign finance — narrow to party / state / cycle
jintel.campaign_finance(
"Acme PAC", cycle=2024,
filter=CampaignFinanceFilterInput(party="DEM", state="CA", min_raised=100_000),
)
# US macro economic series
jintel.macro_series("UNRATE",
filter=ArrayFilterInput(since="2000-01-01", limit=300))
jintel.macro_series_batch(["GDPC1", "CPIAUCSL"],
filter=ArrayFilterInput(since="2010-01-01"))
Defaults
| Filter | Default limit | Default sort |
|---|---|---|
ArrayFilterInput |
20 | DESC |
NewsFilterInput |
20 | DESC (by date) |
ExecutivesFilterInput |
20 | PAY_DESC |
InsiderTradeFilterInput |
20 | DESC (by transactionDate) |
EarningsFilterInput |
20 | DESC (by reportDate) |
SegmentRevenueFilterInput |
20 | DESC (by filingDate) |
TopHoldersFilterInput |
20 (offset 0) | DESC (by value) |
FinancialStatementFilterInput |
20 | DESC (by periodEnding) |
SanctionsFilterInput |
20 | DESC (by score) |
CampaignFinanceFilterInput |
20 | DESC (by totalRaised) |
FilingsFilterInput |
20 | DESC |
RiskSignalFilterInput |
20 | DESC |
FuturesCurveFilterInput |
50 | ASC |
OptionsChainFilterInput |
100 | EXPIRATION_ASC |
ClinicalTrialFilterInput |
20 | DESC (by startDate) |
FdaEventFilterInput |
20 | DESC (by reportDate) |
LitigationFilterInput |
20 | DESC (by dateFiled) |
GovernmentContractFilterInput |
20 | DESC (by actionDate) |
Omitting a filter on a sub-graph returns the full upstream set with that input's defaults applied.
Breaking changes in 0.21
top_holders: { limit, offset }option removed. Usetop_holders_filter=TopHoldersFilterInput(...)instead.- The generic
filteroption no longer threads intonews,insiderTrades,earnings,segmentedRevenue, orfinancials.*— use the new domain-specific filter options above. The Python client raisesJintelValidationErrorat call time if you mix them.
Batch enrichment
batch_enrich accepts up to 20 tickers and pushes server-side loaders to batch and deduplicate upstream calls:
batch = jintel.batch_enrich(
["AAPL", "MSFT", "GOOG"],
fields=["market", "news", "technicals"],
options=EnrichOptions(filter=ArrayFilterInput(limit=5)),
)
Point-in-time queries (as_of)
Pass as_of (ISO 8601) to bound a query to data that was knowable at that timestamp — no lookahead bias in backtests. Set it per call or as a client-wide default.
# Per-call — overrides any default.
aapl_last_summer = jintel.batch_enrich(
["AAPL"],
fields=["news", "institutionalHoldings"],
options=EnrichOptions(
as_of="2023-08-15T00:00:00Z",
filter=ArrayFilterInput(limit=5),
),
)
# Or once at construction — locks the entire client to a replay date.
replay = JintelClient(
api_key=os.environ["JINTEL_API_KEY"],
as_of="2023-08-15T00:00:00Z",
)
Every PIT response carries extensions.asOf.fields with the per-field policy:
from jintel import parse_as_of_extension
envelope = jintel.raw_request(
"""query Q($t: [String!]!, $a: String) {
quotes(tickers: $t, asOf: $a) { ticker }
}""",
{"t": ["AAPL"], "a": "2023-08-15T00:00:00Z"},
)
ext = parse_as_of_extension(envelope)
if ext is not None:
for path, policy in ext.fields.items():
print(path, policy.klass)
# Entity.market.quote → AsOfFieldClass.UNSUPPORTED
SUPPORTED fields honor as_of honestly. BEST_EFFORT fields run with a documented caveat. UNSUPPORTED fields (live quotes, current fundamentals, OFAC SDN, derivatives, etc.) return null/[] rather than serve current data — quotes() returns list[MarketQuote | None] so callers must handle the gap. Cached responses are bucketed by as_of, so PIT and live requests never share a slot.
Response caching
Pass cache=True to enable an in-process TTL cache (30 s for quotes, 5 min for enrich / price history). Eliminates redundant HTTP when the same data is requested in a short window.
from jintel import JintelClient, CacheConfig
jintel = JintelClient(
api_key=os.environ["JINTEL_API_KEY"],
cache=CacheConfig(quotes_ttl_ms=15_000, enrich_ttl_ms=120_000),
)
jintel.invalidate_cache(["AAPL"]) # after an external signal event
print(jintel.cache_stats()) # CacheStats(hits=12, misses=3, evictions=0, size=15)
The cache is in-memory by default. To plug a Redis-backed implementation, satisfy the CacheBackend Protocol and pass it as cache_backend=... (the SDK does not ship a Redis backend in v0).
Payment modes (Bearer + x402)
Two ways to pay for queries on the same endpoint:
Bearer (prepaid credits) — pass api_key and you're done. This is the default mode and matches the TS client.
x402 (per-call USDC on Base) — pass a payment_signer callable. On any 402 Payment Required response, the SDK decodes the PAYMENT-REQUIRED header into a typed X402Quote, calls your signer to produce a base64 EIP-3009 USDC authorization, retries the request with PAYMENT-SIGNATURE, then exposes the PAYMENT-RESPONSE settlement record on client.last_settlement.
from jintel import JintelClient, X402Quote
def my_signer(quote: X402Quote) -> str:
# Use eth-account, web3.py, or an MCP wallet — produce a base64 PaymentPayload.
...
# x402-only — no Bearer key needed
with JintelClient(payment_signer=my_signer) as jintel:
result = jintel.quotes(["AAPL"])
print(jintel.last_settlement) # X402SettlementResponse | None
# Both modes — Bearer settles by default; x402 fires on CREDITS_EXHAUSTED
with JintelClient(api_key=os.environ["JINTEL_API_KEY"], payment_signer=my_signer) as jintel:
...
When you don't supply a signer, a 402 raises JintelPaymentRequiredError(quote: X402Quote) so callers can handle the payment manually. When the Bearer path runs out of credits, the server returns the GraphQL error code CREDITS_EXHAUSTED, which the SDK surfaces as JintelCreditsExhaustedError.
This SDK does not embed wallet logic — payment_signer is the integration seam. See examples/x402_payment.py for a worked example using eth-account.
Pattern-matching JintelResult
Python's match/case makes the discriminated union ergonomic — Python-specific addition over the TS surface:
from jintel import Ok, Err
match jintel.gdp("USA"):
case Ok(data=points):
latest = points[0]
print(f"GDP {latest.date}: {latest.value}")
case Err(error=msg):
print(f"GDP query failed: {msg}")
.unwrap() returns the data or raises JintelError. .map(fn) transforms the success branch. .value_or(default) returns the data or a fallback.
License
MIT
Metadata
Release files for jintel 0.29.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 | Provenance | Actions |
|---|---|---|---|---|
| jintel-0.29.0.tar.gz | 40.4 kB | With provenance | Details for jintel-0.29.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| jintel-0.29.0-py3-none-any.whl | Python 3 | none | any | Details for jintel-0.29.0-py3-none-any.whl |
Total release size: 84.5 kB
Release files / jintel-0.29.0.tar.gz
| Download URL | jintel-0.29.0.tar.gz |
|---|---|
| Size | 40.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eeaf29dd0290591734a0020bf78f5a5f18cad2d860dd043c1ae4fc2308328c7a
|
|
BLAKE2b-256 checksum How to use checksums |
6249d78fa2ee5fe9d2b7d0ebab03ccd92a58d2112ba8347d3f0047d7c62964e5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Apr 27, 2026.
Transparency logRelease files / jintel-0.29.0-py3-none-any.whl
| Download URL | jintel-0.29.0-py3-none-any.whl |
|---|---|
| Size | 44.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
97e264023229e2ac4b5ee0793b5a3d3eaade1a23b3aa3e05fe34fe864df537ee
|
|
BLAKE2b-256 checksum How to use checksums |
616055ffdfe629f6aa5f29c0c0c95c8ed23319d0463868dd931dc79ec6ea7e27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Apr 27, 2026.
Transparency logSecurity for jintel 0.29.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for jintel-0.29.0.tar.gz
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 Apr 27, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.