Skip to main content

Market research API for AI agents

A research agent that writes a one-page note on a ticker needs the same data a human analyst does: the current quote, the valuation multiples, what analysts think the price should be, what the news cycle looks like, what the technicals are saying, and any recent insider activity. The hard part of building this isn't the prompt — it's the data plumbing. A typical vendor stack for this is four or five separate API contracts.

Jintel returns all of it in a single GraphQL request. This page shows the shape of that request and the prompt template that turns it into a structured bull / bear case.

What an LLM needs to write a credible case

A research note has two halves. The bull case asks: is this company growing, profitable, getting upgraded, with positive news flow and constructive technicals? The bear case asks: is the multiple stretched, are margins compressing, are insiders selling, is the news flow turning negative, are technicals breaking down?

Mapping that to schema fields:

QuestionField
Is it growing?Fundamentals.revenueGrowth, Fundamentals.earningsGrowth
Is it profitable?Fundamentals.grossMargin, Fundamentals.operatingMargin, Fundamentals.netMargin, Fundamentals.returnOnEquity
Is the multiple stretched?Fundamentals.peRatio, Fundamentals.forwardPE, Fundamentals.pegRatio, Fundamentals.priceToSales, Fundamentals.evToEbitda
What do analysts think?AnalystConsensus.targetMean, AnalystConsensus.recommendation, AnalystConsensus.numberOfAnalysts
What is the news cycle saying?Entity.newstitle, sentimentScore, date
Are technicals supportive or stretched?TechnicalIndicators.rsi, ema50, sma200, macd.histogram, bollingerBands
Are insiders signaling something?Entity.insiderTradesacquiredDisposed, transactionValue, isOfficer
Recent earnings beat or miss?Entity.earningssurprisePercent, epsActual, epsEstimate

Every one of those fields lives on Entity or one of its sub-graphs, which means a single entitiesByTickers query covers all of it.

The query

query ResearchPack($tickers: [String!]!) {
entitiesByTickers(tickers: $tickers) {
name
tickers
market {
quote { price changePercent volume marketCap }
fundamentals {
sector industry
peRatio forwardPE pegRatio priceToSales evToEbitda
revenueGrowth earningsGrowth
grossMargin operatingMargin netMargin returnOnEquity
beta fiftyTwoWeekHigh fiftyTwoWeekLow
}
}
analyst {
recommendation recommendationMean numberOfAnalysts
targetMean targetHigh targetLow
}
technicals {
rsi ema50 sma200
macd { histogram }
bollingerBands { upper middle lower }
}
news(filter: { limit: 8, sort: DESC }) {
title date source sentimentScore link
}
insiderTrades(filter: { limit: 5 }) {
reporterName officerTitle isOfficer
transactionDate acquiredDisposed shares pricePerShare transactionValue
}
earnings(filter: { onlyReported: true, limit: 4 }) {
reportDate epsActual epsEstimate surprisePercent
}
}
}

That's seven domains for any number of tickers in a single round trip.

Prompt template

Wrap the response in this prompt and you have a working research-agent loop. The model gets enough structured signal to write distinct, defensible bull and bear cases instead of hand-waving.

You are an equity research analyst. Given the JSON below, write a structured
note on each ticker with two sections:

1. BULL CASE — a 3-5 bullet argument for owning the stock at today's price.
Cite specific fields (e.g. "forward PE of 18 vs sector median, revenue
growth of 22% YoY, analyst target +14% above spot").
2. BEAR CASE — a 3-5 bullet counter. Cite specific fields (e.g. "EV/EBITDA
of 35 vs 5y median of 22, RSI 78 = overbought, three insider sales in
last 30 days totalling $12M").

Constraints:
- Cite only fields present in the JSON. Do not invent numbers.
- If a field is null, say so — do not substitute a related field.
- News sentiment scores: > 0.2 is positive, < -0.2 is negative.
- Do not give a buy/sell/hold recommendation — just present both cases.

Data:
{{JINTEL_RESPONSE_JSON}}

Code

import { JintelClient } from "@yojinhq/jintel-client";
import Anthropic from "@anthropic-ai/sdk";

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

const RESEARCH_QUERY = `
query($t: [String!]!) {
entitiesByTickers(tickers: $t) {
name tickers
market {
quote { price changePercent marketCap }
fundamentals {
sector peRatio forwardPE pegRatio priceToSales evToEbitda
revenueGrowth earningsGrowth
grossMargin operatingMargin netMargin returnOnEquity
}
}
analyst { recommendation targetMean targetHigh targetLow numberOfAnalysts }
technicals { rsi ema50 sma200 macd { histogram } }
news(filter: { limit: 6, sort: DESC }) { title date sentimentScore }
insiderTrades(filter: { limit: 5 }) {
reporterName officerTitle transactionDate acquiredDisposed transactionValue
}
earnings(filter: { onlyReported: true, limit: 4 }) {
reportDate epsActual epsEstimate surprisePercent
}
}
}
`;

async function researchNote(tickers: string[]) {
const { data } = await jintel.request(RESEARCH_QUERY, { t: tickers });

const msg = await claude.messages.create({
model: "claude-opus-4-7",
max_tokens: 2048,
messages: [{
role: "user",
content: `Write a bull / bear case for each ticker.\n\nData:\n${JSON.stringify(data, null, 2)}`,
}],
});

return msg.content[0].type === "text" ? msg.content[0].text : "";
}

console.log(await researchNote(["NVDA", "AMD"]));

Where to go next

  • Sub-graphs — the full nested-field map (financial statements, segmented revenue, top holders, etc.).
  • Filtering — how ArrayFilterInput, NewsFilterInput, and EarningsFilterInput slice array sub-graphs.
  • Market data — fundamentals and analyst consensus reference.
  • News — date and sentiment filters.