Python client for the Jintel intelligence API
Project description
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).
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jintel-0.28.0.tar.gz.
File metadata
- Download URL: jintel-0.28.0.tar.gz
- Upload date:
- Size: 37.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9cf335a0964f8ef111139a431f6784fb884223ecf85fc12b3ccf3c6c0c91dc0
|
|
| MD5 |
bc914d92bb2f7a94bd25c8e6516b6897
|
|
| BLAKE2b-256 |
49fe87e83f15a5440df686c8b17240929d293d8248826e6e145ef6e70577a67d
|
Provenance
The following attestation bundles were made for jintel-0.28.0.tar.gz:
Publisher:
release.yml on YojinHQ/jintel-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jintel-0.28.0.tar.gz -
Subject digest:
b9cf335a0964f8ef111139a431f6784fb884223ecf85fc12b3ccf3c6c0c91dc0 - Sigstore transparency entry: 1390604965
- Sigstore integration time:
-
Permalink:
YojinHQ/jintel-py@858cf016d11dc99af8ed1a633831cc6c85ecae4f -
Branch / Tag:
refs/tags/v0.28.0 - Owner: https://github.com/YojinHQ
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@858cf016d11dc99af8ed1a633831cc6c85ecae4f -
Trigger Event:
push
-
Statement type:
File details
Details for the file jintel-0.28.0-py3-none-any.whl.
File metadata
- Download URL: jintel-0.28.0-py3-none-any.whl
- Upload date:
- Size: 41.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd1e1d16267c5cb03876ab7450d7f28b39c7102681a46cf4224f76f5a71a377c
|
|
| MD5 |
3caf85262b55b10d828d94ddc5bbe6b1
|
|
| BLAKE2b-256 |
ec90fe0ade05737d86ec450feb86be45c4465ab3bd040726970e4ed0274f9282
|
Provenance
The following attestation bundles were made for jintel-0.28.0-py3-none-any.whl:
Publisher:
release.yml on YojinHQ/jintel-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jintel-0.28.0-py3-none-any.whl -
Subject digest:
dd1e1d16267c5cb03876ab7450d7f28b39c7102681a46cf4224f76f5a71a377c - Sigstore transparency entry: 1390604974
- Sigstore integration time:
-
Permalink:
YojinHQ/jintel-py@858cf016d11dc99af8ed1a633831cc6c85ecae4f -
Branch / Tag:
refs/tags/v0.28.0 - Owner: https://github.com/YojinHQ
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@858cf016d11dc99af8ed1a633831cc6c85ecae4f -
Trigger Event:
push
-
Statement type: