Skip to main content

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. Use top_holders_filter=TopHoldersFilterInput(...) instead.
  • The generic filter option no longer threads into news, insiderTrades, earnings, segmentedRevenue, or financials.* — use the new domain-specific filter options above. The Python client raises JintelValidationError at 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jintel-0.28.0.tar.gz (37.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jintel-0.28.0-py3-none-any.whl (41.0 kB view details)

Uploaded Python 3

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

Hashes for jintel-0.28.0.tar.gz
Algorithm Hash digest
SHA256 b9cf335a0964f8ef111139a431f6784fb884223ecf85fc12b3ccf3c6c0c91dc0
MD5 bc914d92bb2f7a94bd25c8e6516b6897
BLAKE2b-256 49fe87e83f15a5440df686c8b17240929d293d8248826e6e145ef6e70577a67d

See more details on using hashes here.

Provenance

The following attestation bundles were made for jintel-0.28.0.tar.gz:

Publisher: release.yml on YojinHQ/jintel-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for jintel-0.28.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dd1e1d16267c5cb03876ab7450d7f28b39c7102681a46cf4224f76f5a71a377c
MD5 3caf85262b55b10d828d94ddc5bbe6b1
BLAKE2b-256 ec90fe0ade05737d86ec450feb86be45c4465ab3bd040726970e4ed0274f9282

See more details on using hashes here.

Provenance

The following attestation bundles were made for jintel-0.28.0-py3-none-any.whl:

Publisher: release.yml on YojinHQ/jintel-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page