How to Research Many Companies at Once with the DeepResearch Batch API
TL;DR
The Valyu DeepResearch Batch API runs the same research brief across many entities in parallel. You create a batch with the shared settings (mode, output format, sources), add one task per company (up to 100 per request), wait, then list every task's report, sources and cost.
We ran one brief across 10 payments companies: latest-year revenue, growth, net income and top risk factors from each 10-K, using only SEC filings. 10 of 10 tasks completed and 0 failed.
The whole batch took 4.9 minutes from creation to the last report (about 24 minutes if run one after another) and cost $1.56 in total. Every revenue and net income figure matched SEC XBRL data. The full script is about 100 lines of Python.
The pattern that matters: save the batch ID, and reconcile every input to a result, rather than trusting a completed-only listing.
The job: one question, every company on the list
An analyst covering payments wants the same four facts for every name in the peer group: fiscal year, revenue and growth, net income, and the risks management stresses most. A VC reviewing a portfolio, a sales team qualifying accounts and a credit team refreshing a watchlist all have the same shape of problem.
Running one DeepResearch task at a time works for three companies. At ten or fifty, you end up writing your own queue, tracking IDs, retrying, and adding up costs. The Batch API does that bookkeeping:
- Shared settings, set once.
mode,output_formatsand search live on the batch. Every task inherits them and cannot override them, so every company is researched the same way. - Per-task inputs. Each task has its own query and can set
research_strategy,report_format,urlsandmetadata, plus your ownid. - One place to track it. Batch status gives task counts (
queued,running,completed,failed,cancelled) and the total cost. - One notification. Pass
webhook_urlto be called when the batch finishes instead of polling. - Shared settings live on the batch. Only the company changes from task to task.
Step 1: Install and set your key
pip install valyu
export VALYU_API_KEY=your-key # from platform.valyu.ai
New accounts get free credits, no credit card required. Keep the key server-side.
Step 2: Write the brief once
The brief is three strings. The query names the company and the job; the strategy says where to look; the report format fixes the shape of the answer so ten reports read the same.
STRATEGY = (
"Use the company's most recent annual report on Form 10-K. "
"Read the income statement and Item 1A. Do not use analyst estimates. "
)
FORMAT = (
"Exactly these sections: Fiscal year; Revenue (with prior-year figure and % change); "
"Net income; Three most emphasised risk factors (one line each); Sources. "
"Cite the filing for every figure. Under 250 words. "
)
Restricting the batch to valyu/valyu-sec-filings keeps every figure traceable to a filing. Drop the restriction if you want web, news or other data sources in the mix.
Step 3: Create the batch and add one task per company
from valyu import Valyu
valyu = Valyu() # reads VALYU_API_KEY
batch = valyu.batch.create(
name="payments-10k-screen",
mode="fast",
output_formats=["markdown"],
search={"included_sources": ["valyu/valyu-sec-filings"]},
)
added = valyu.batch.add_tasks(
batch.batch_id,
[
{
"id": key, # your own ID, returned on every result
"query": f"{name}: latest fiscal-year revenue, revenue growth, net income "
"and top three risk factors from its most recent 10-K.",
"research_strategy": STRATEGY,
"report_format": FORMAT,
}
for key, name in COMPANIES.items()
]
)
print(added.added, "tasks queued in ", batch.batch_id)
COMPANIES is a plain dict, {"visa": "Visa (V)", "mastercard": "Mastercard (MA)", ...}. Use your own short keys as task IDs; they come back on every result, which makes reconciliation trivial.
Two things worth knowing before you add tasks:
- Billing is all-or-nothing per add request. Adding tasks holds the mode price of every task in that request. If your balance is too low you get a 402 and none are added. Creating the batch itself is free.
- Pick the mode for the job.
fastis 25 credits per task and is the mode the docs suggest for batches.standardis 100,heavy500. Our brief is a narrow lookup, so fast fits. - Save batch.batch_id before you do anything else. If your process dies, the research keeps running and you collect it later with the ID.
Step 4: Wait, then reconcile every task
final = valyu.batch.wait_for_completion(
batch_id,
poll_interval=15,
on_progress=lambda s: print(f"{s.batch.counts.completed} / {s.batch.counts.total} done"),
)
tasks, last_key = [], None
while True: # results are paginated
page = valyu.batch.list_tasks(batch_id, include_output=True, last_key=last_key)
tasks += page.tasks or []
if not page.pagination.has_more:
break
last_key = page.pagination.last_key
for task in tasks:
if task.status == "completed":
Path(f"reports/{task.task_id}.md").write_text(str(task.output))
print(task.task_id, task.status, len(task.sources or []), task.cost, task.error or "")
The loop lists every task, not just completed ones. A status="completed" filter silently drops failed and cancelled tasks, so a clean-looking results page is not proof the whole batch succeeded. The full script writes one row per company to reports/index.csv: status, source count, cost, report file and any error.
wait_for_completion returns on completed or completed_with_errors, and raises if the batch is cancelled or the wait times out. Failed tasks are not charged.
What happened when we ran it
We ran the brief across 10 US-listed payments companies in fast mode today.
- Run Result
- Tasks: 10
- Completed / failed: 10 / 0
- Time to the last report: 4.9 min
- Sum of individual task times: 24.4 min
- Total cost: $1.56 ($0.156 per task)
All 10 tasks were running within seconds of being added. Run one after another, the same tasks would have taken about 24 minutes; in the batch, the slowest task set the pace.
- Company Status Task time Sources cited What the report says (reviewed against its cited filing)
- Visa completed 4.8 min 2 FY to 30 Sep 2025: revenue $40.0bn, +11%; net income $20.1bn, +2%
- Mastercard completed 1.5 min 1 FY2025: revenue $32.8bn, +16%; net income $15.0bn, +16%
- American Express completed 1.7 min 2 FY2025: revenue net of interest expense $72.2bn, +10%; net income $10.8bn, +7%
- PayPal completed 3.1 min 1 FY2025: revenue $33.2bn, +4.3% (computed); net income $5.2bn
- Block completed 2.4 min 2 FY2025: revenue $24.2bn, +0.3%; net income $1.3bn vs $2.9bn, and flags a ~$1.9bn tax benefit in 2024
- Fiserv completed 1.8 min 2 FY2025: revenue $21.2bn, +3.6%; net income attributable $3.5bn
- Global Payments completed 1.8 min 2 FY2025: revenue $7.7bn, -0.4%, continuing operations only; flags Issuer Solutions as discontinued
- FIS completed 2.4 min 2 FY2025: revenue $10.7bn, +5%; net earnings attributable $382m vs $1,450m, with Worldpay recast as discontinued
- Affirm completed 3.6 min 2 FY to 30 Jun 2026: revenue $4.3bn, +32.1% (computed); net income $1.9bn, mostly a $1.4bn tax benefit, which the report calls out
- Toast completed 1.2 min 1 FY2025: revenue $6.2bn, +24%; net income $342m vs $19m
We checked every revenue and net income figure, current and prior year, against the companies' XBRL data on SEC EDGAR. All 40 numbers matched.
The reports also did things we did not ask for: six said the filing does not rank risk factors and explained which ones they picked, and four flagged a comparability problem (a one-off tax benefit, discontinued operations or a recast prior year). Most tasks cited one or two filings because the batch was limited to SEC filings, which is what you want for this kind of brief.
Note the fiscal years differ: Visa's ends in September and Affirm's in June, which a naive spreadsheet would silently mix.
Here is the Global Payments report as it came back. We only changed the formatting: headings became bold text, dashes became commas and the source list is cut:
Global Payments FY2025: Revenue, Net Income and Leading Risk Factors
Fiscal year
FY2025, ended December 31, 2025; Form 10-K filed February 20, 2026 [1].
Revenue
$7,705.9 million, down $30.1 million, or 0.4%, from $7,736.0 million in FY2024 [2]. Figures cover continuing operations (Merchant Solutions); Issuer Solutions is reported as a discontinued operation, so FY2025 revenue is not comparable to pre-2025 total-company revenue [1].
Net income
$1,456.0 million, versus $1,644.2 million in FY2024 [1]. Net income attributable to Global Payments was $1,400.1 million, after $55.9 million attributable to noncontrolling interests [1].
Three most emphasised risk factors
- Inability to protect systems and data from evolving cybersecurity threats [1].
- Software and hardware defects, failures, undetected errors and development delays [1].
- Dependence on third-party relationships and key supplier agreements [1].
These are the first three bullets of the Item 1A summary; the filing does not rank risks [1].
Batch or Individual tasks?
Batches trade flexibility for consistency. Every task inherits the batch's mode, output formats and search settings, and some features are not available in a batch at all.
Batch tasks inherit mode, output formats and search settings. Anything task-specific belongs in an individual task.
You need Use:
- The same brief across 10 to 100+ entities - Batch
- One total cost and one completion webhook - Batch
- CSV, Excel, PowerPoint or Word deliverables - Individual task
- Your own files, MCP servers or previous reports - Individual task
- Human review checkpoints before research continues - Individual task
- A heavier mode for one company in the list - A separate batch, or an individual task
Code execution is always on in batch tasks.
Production notes
Use a webhook instead of polling. Pass webhook_url to batch.create. The webhook_secret is returned only on creation, so store it immediately and verify signatures.
More than 100 tasks? Call add_tasks again on the same batch while it is open or processing.
Keep your inputs. Store each submitted query with its task ID so you can match every input to a completed, failed or cancelled result.
Check the figures. A cited report is inspectable, not audited. Spot-check numbers against the cited filing, especially fiscal-year ends and restated figures, before they go into a model or a memo.
Frequently asked questions
How many tasks can a DeepResearch batch hold?
Each add_tasks request accepts 1 to 100 tasks, and you can add more requests to the same batch while it is open or processing. Actual concurrency depends on your account limits and capacity.
How much does a batch cost?
Each task costs its mode price: 25 credits in fast, 100 in standard, 500 in heavy, 2,500 in max. Creating a batch is free, failed tasks are not charged, and the batch cost field is the sum of its tasks. See Valyu pricing.
Can each task in a batch use different settings?
Partly. Each task can set its own query, research_strategy, report_format, urls and metadata. mode, output_formats and search come from the batch and cannot be overridden per task.
Can a batch return structured JSON instead of markdown?
The batch's output_formats accepts a JSON Schema in place of markdown, applied to every task. You cannot mix a schema with markdown or pdf. We used markdown here; test a schema on a few tasks before scaling.
Is the Batch API different from running DeepResearch in a loop?
The research is the same. The difference is bookkeeping: shared settings, one status endpoint with counts and total cost, pagination over results, cancel for the whole batch and one webhook at the end.
Can I use TypeScript instead of Python?
Yes. The valyu-js SDK has the same methods (valyu.batch.create, addTasks, listTasks). See the TypeScript batch reference.
Run your own list
Pick a list you already maintain by hand: a peer group, a portfolio, a watchlist, a set of target accounts. Write the brief once, run it in fast mode on five names, read the reports, then scale up. The complete script, with start and collect commands, is below.
""" Run one DeepResearch brief across a list of companies with the Valyu Batch API. """
import csv
import sys
from pathlib import Path
from valyu import Valyu
COMPANIES = {
"visa": "Visa (V)",
"mastercard": "Mastercard (MA)",
"amex": "American Express (AXP)",
"paypal": "PayPal (PYPL)",
"block": "Block (XYZ)",
"fiserv": "Fiserv (FISV)",
"global-payments": "Global Payments (GPN)",
"fis": "Fidelity National Information Services (FIS)",
"affirm": "Affirm (AFRM)",
"toast": "Toast (TOST)",
}
STRATEGY = (
"Use the company's most recent annual report on Form 10-K. "
"Read the income statement and Item 1A. Do not use analyst estimates. "
)
FORMAT = (
"Exactly these sections: Fiscal year; Revenue (with prior-year figure and % change); "
"Net income; Three most emphasised risk factors (one line each); Sources. "
"Cite the filing for every figure. Under 250 words. "
)
OUT = Path("reports")
def start(valyu: Valyu) -> str:
batch = valyu.batch.create(
name="payments-10k-screen",
mode="fast",
output_formats=["markdown"],
search={"included_sources": ["valyu/valyu-sec-filings"]},
)
if not batch.success:
sys.exit(batch.error)
added = valyu.batch.add_tasks(
batch.batch_id,
[
{
"id": key,
"query": f"{name}: latest fiscal-year revenue, revenue growth, net income "
"and top three risk factors from its most recent 10-K.",
"research_strategy": STRATEGY,
"report_format": FORMAT,
}
for key, name in COMPANIES.items()
]
)
if not added.success:
sys.exit(added.error)
print(f"{added.added} tasks queued in {batch.batch_id}")
return batch.batch_id
def all_tasks(valyu: Valyu, batch_id: str
Comments
No comments yet. Start the discussion.