npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@yojinhq/jintel-client

v0.31.0

Published

TypeScript client for the Jintel intelligence API

Readme

@yojinhq/jintel-client

TypeScript client for the Jintel intelligence API. Typed queries, Zod-validated responses, an optional response cache, and a dynamic query builder that threads per-field filters into the underlying GraphQL.

Install

npm install @yojinhq/jintel-client

Quick start

import { JintelClient } from '@yojinhq/jintel-client';

const jintel = new JintelClient({ apiKey: process.env.JINTEL_API_KEY! });

const quotes = await jintel.quotes(['AAPL', 'MSFT', 'BTC']);
const aapl = await jintel.enrichEntity('AAPL', ['market', 'news', 'analyst']);

All public methods return JintelResult<T> — a discriminated union { success: true, data } | { success: false, error } — so errors never throw. Use jintel.request(query, variables) for arbitrary GraphQL.

Agent / pay-per-query (x402 v2)

The Jintel API also accepts x402 v2 — sign an EIP-3009 USDC authorization on Base instead of presenting an API key. The server quotes each query from its GraphQL AST (floor $0.015, up to ~$10 for very large fan-outs).

The client doesn't bundle a wallet. Pass an x402-fetch-wrapped fetch and skip apiKey:

import { JintelClient } from '@yojinhq/jintel-client';
import { wrapFetchWithPayment } from 'x402-fetch';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount(process.env.WALLET_KEY as `0x${string}`);
const jintel = new JintelClient({
  fetch: wrapFetchWithPayment(fetch, account),
});

// First call triggers a 402; the wrapper signs and retries automatically.
const quotes = await jintel.quotes(['AAPL', 'MSFT']);

If you call without a wrapper and the server returns 402, the SDK throws a typed JintelPaymentRequiredError carrying the parsed X402Quote (network, asset, payTo, amount, maxTimeoutSeconds) so you can sign and retry yourself:

import { JintelPaymentRequiredError } from '@yojinhq/jintel-client';

try {
  await jintel.request('{ marketStatus { isOpen } }');
} catch (err) {
  if (err instanceof JintelPaymentRequiredError) {
    console.log(err.quote?.accepts[0]); // pay-to address, USDC amount, etc.
  }
}

Filtering array sub-graphs

Most array fields accept an optional filter argument. The generic ArrayFilterInput covers date range + limit + sort; many sub-graphs have domain-specific inputs with extra dimensions.

// Generic ArrayFilterInput — research, predictions, discussions, social, institutionalHoldings,
// earningsPressReleases, periodicFilings, market.history/keyEvents/shortInterest, economics.
await jintel.enrichEntity('AAPL', ['research', 'market'], {
  filter: { since: '2025-01-01', limit: 10, sort: 'DESC' },
});

// NewsFilterInput — filter by source and sentiment score
await jintel.enrichEntity('AAPL', ['news'], {
  newsFilter: { minSentiment: 0, limit: 10 },
});

// ExecutivesFilterInput — top-paid officers only
await jintel.enrichEntity('AAPL', ['executives'], {
  executivesFilter: { minPay: 1_000_000, sortBy: 'PAY_DESC', limit: 5 },
});

// InsiderTradeFilterInput — directors only, acquisitions >= $100k
await jintel.enrichEntity('AAPL', ['insiderTrades'], {
  insiderTradesFilter: { isDirector: true, acquiredDisposed: 'ACQUIRED', minValue: 100_000 },
});

// EarningsFilterInput — upcoming periods only, or reported beats >= 5%
await jintel.enrichEntity('AAPL', ['earnings'], {
  earningsFilter: { onlyReported: true, minSurprisePercent: 5 },
});

// SegmentRevenueFilterInput — product breakdown >= $1B
await jintel.enrichEntity('AAPL', ['segmentedRevenue'], {
  segmentedRevenueFilter: { dimensions: ['PRODUCT'], minValue: 1_000_000_000 },
});

// TopHoldersFilterInput — paginated top-holder lookup (replaces the old `topHolders` limit/offset)
await jintel.enrichEntity('AAPL', ['topHolders'], {
  topHoldersFilter: { limit: 25, offset: 0, minValue: 50_000 },
});

// FinancialStatementFilterInput — annual only
await jintel.enrichEntity('AAPL', ['financials'], {
  financialStatementsFilter: { periodTypes: ['12M'], limit: 5 },
});

// SanctionsFilterInput + CampaignFinanceFilterInput — on regulatory
await jintel.enrichEntity('Gazprom', ['regulatory'], {
  sanctionsFilter: { minScore: 80, programs: ['SDGT'] },
  campaignFinanceFilter: { cycle: 2024, party: 'DEM' },
});

// FilingsFilterInput — narrow SEC filings by form type
await jintel.enrichEntity('AAPL', ['regulatory'], {
  filingsFilter: { types: ['FILING_10K', 'FILING_10Q'], limit: 5 },
});

// RiskSignalFilterInput — drop low-severity noise
await jintel.enrichEntity('Gazprom', ['risk'], {
  riskSignalFilter: { severities: ['HIGH', 'CRITICAL'] },
});

// OptionsChainFilterInput — options chains can exceed 5 000 rows; filter aggressively
await jintel.enrichEntity('BTC', ['derivatives'], {
  optionsFilter: {
    optionType: 'CALL',
    strikeMin: 60_000,
    strikeMax: 80_000,
    minOpenInterest: 100,
    sort: 'VOLUME_DESC',
    limit: 25,
  },
});

// FuturesCurveFilterInput — defaults to ASC (nearest contract first)
await jintel.enrichEntity('BTC', ['derivatives'], {
  futuresFilter: { 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.

Top-level queries

Economics and short-interest queries accept the generic ArrayFilterInput. sanctionsScreen and campaignFinance accept their domain-specific filters:

await jintel.gdp('USA', 'REAL', { since: '2010-01-01', limit: 20 });
await jintel.inflation('USA', { since: '2020-01-01' });
await jintel.shortInterest('GME', { limit: 5 });

// Root sanctions screen — filter by score, list, or program
await jintel.sanctionsScreen('Gazprom', 'RU', { minScore: 80, listNames: ['SDN'] });

// Root campaign finance — narrow to party / state / cycle
await jintel.campaignFinance('Acme PAC', 2024, { party: 'DEM', state: 'CA', minRaised: 100_000 });

// US macro economic series — observations are filterable (ArrayFilterInput)
await jintel.macroSeries('UNRATE', { since: '2000-01-01', limit: 300 });
await jintel.macroSeriesBatch(['GDPC1', 'CPIAUCSL'], { 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 filter on a sub-graph returns the full upstream set with that input's defaults applied.

Breaking changes in 0.21

  • topHolders: { limit, offset } option removed. Use topHoldersFilter: { limit, offset, minValue, since, until, sort } instead.
  • The generic filter option no longer threads into news, insiderTrades, earnings, segmentedRevenue, or financials.* — use the new domain-specific filter options above.

Batch enrichment

batchEnrich accepts up to 20 tickers and pushes server-side loaders to batch and deduplicate upstream calls:

const batch = await jintel.batchEnrich(
  ['AAPL', 'MSFT', 'GOOG'],
  ['market', 'news', 'technicals'],
  { filter: { limit: 5 } },
);

Point-in-time queries (asOf)

Pass asOf (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.
const aaplLastSummer = await jintel.batchEnrich(
  ['AAPL'],
  ['news', 'institutionalHoldings'],
  { asOf: '2023-08-15T00:00:00Z', filter: { limit: 5 } },
);

// Or once at construction — locks the entire client to a replay date.
const replay = new JintelClient({
  apiKey: process.env.JINTEL_API_KEY!,
  asOf: '2023-08-15T00:00:00Z',
});

Every PIT response carries extensions.asOf.fields with the per-field policy:

const { extensions } = await jintel.rawRequest<{ quotes: unknown }>(
  `query Q($t: [String!]!, $a: String) { quotes(tickers: $t, asOf: $a) { ticker } }`,
  { t: ['AAPL'], a: '2023-08-15T00:00:00Z' },
);
console.log(extensions?.asOf?.fields);
// { 'MarketData.quote': { class: 'UNSUPPORTED', warning: 'Source serves a live state...' } }

SUPPORTED fields honor asOf 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 (MarketQuote | null)[] so callers must handle the gap. Cached responses are bucketed by asOf, 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.

const jintel = new JintelClient({
  apiKey: process.env.JINTEL_API_KEY!,
  cache: { quotesTtlMs: 15_000, enrichTtlMs: 120_000 },
});

jintel.invalidateCache(['AAPL']); // after an external signal event

License

MIT