Build a Source-Linked News Briefing with Currents Search API
Build a Python briefing workflow that calls Currents Search API, keeps publisher URLs and published times attached, and outputs source-linked Markdown or JSON for an agent, dashboard, or internal daily brief.
Most agents and dashboards do not automatically know what happened this morning. They need a retrieval step that can bring in fresh articles, preserve where each item came from, and keep enough metadata attached for a person or model to check the result.
This guide builds that step with Currents Search API. The output is a source-linked briefing you can read directly, store as JSON, or pass into your own agent workflow.
Currents provides the news data layer: article JSON, publisher URLs, published times, and filters such as keywords, language, country, category, date range, and domain. Your application owns the prompt, model call, summaries, alerts, embeddings, storage, and business logic.
A concrete briefing workflow
One practical version of this pattern is a research assistant for an operating team.
Imagine a team tracking an energy storage supplier because it affects customers, procurement plans, and policy exposure. The watchlist is not just the company name. It includes battery materials, grid storage projects, key executives, regulators, financing terms, and a few customer names.
Each morning, the assistant runs a small set of Currents searches. It keeps the title, description, URL, published time, language, and category for each article. Then it produces a short brief with source links still attached.
If the team asks for a deep dive, the same system can narrow the search window, add domain filters, and pass only the selected articles into an LLM prompt. The model can help organize the briefing, but it should not invent facts beyond the article set.
That is the core design: use Currents to collect current, source-linked news context, then let your own application decide how to format, summarize, store, or route it.
What we are building
The script in this article will:
- Accept a small watchlist of topics.
- Call Currents Search API for each topic.
- Keep the article title, description, URL, published time, language, and category.
- Print a Markdown briefing with links.
- Optionally save the same data as JSON for an agent or dashboard.
The example uses direct HTTP requests in Python so every request parameter is visible.
Prerequisites
You need:
- Python 3.8 or higher
- A Currents API key from the dashboard
- The
requestspackage
Install the dependency:
pip install requests
Set your API key:
export CURRENTS_API_KEY="your-api-key-here"
Step 1: call Search API
Create briefing.py:
import os
import requests
API_KEY = os.environ["CURRENTS_API_KEY"]
SEARCH_URL = "https://api.currentsapi.services/v1/search"
def search_news(keywords, *, language="en", page_size=5):
response = requests.get(
SEARCH_URL,
params={
"keywords": keywords,
"language": language,
"page_number": 1,
"page_size": page_size,
"apiKey": API_KEY,
},
timeout=20,
)
response.raise_for_status()
data = response.json()
if data.get("status") != "ok":
raise RuntimeError(data)
return data.get("news", [])
Currents returns articles under the news key. Each article can include fields such as id, title, description, url, author, image, language, category, and published.
Step 2: define a watchlist
Keep the first version simple. Start with a few searches that represent the questions your team actually asks.
WATCHLIST = {
"Company developments": "grid battery supplier",
"Policy and regulation": "energy storage regulation",
"Customer and project news": "utility scale battery project",
}
For a private analyst workflow, this might be a portfolio company, a customer segment, a competitor set, or a policy topic. For a PR team, it might be a brand name, executive names, product categories, and incident terms.
Step 3: normalize article fields
The briefing should keep the source URL and publication time close to every claim. Do not strip those fields too early.
def normalize_article(article):
return {
"title": article.get("title") or "Untitled",
"description": article.get("description") or "",
"url": article.get("url") or "",
"published": article.get("published") or "",
"language": article.get("language") or "",
"category": article.get("category") or [],
}
If your UI needs a publisher label, derive it from the article URL or your own source table. Do not assume a separate source field unless your live response confirms it.
Step 4: build the Markdown briefing
Now collect the searches and print a source-linked briefing:
from datetime import datetime, timezone
def build_briefing():
sections = []
collected = {}
for section, query in WATCHLIST.items():
articles = [normalize_article(item) for item in search_news(query)]
collected[section] = articles
lines = [f"## {section}", ""]
if not articles:
lines.append("No matching articles found.")
for article in articles:
title = article["title"]
url = article["url"]
published = article["published"]
description = article["description"]
if url:
lines.append(f"- {title} - <{url}>")
else:
lines.append(f"- {title}")
if published:
lines.append(f" - Published: {published}")
if description:
lines.append(f" - {description}")
sections.append("\n".join(lines))
generated_at = datetime.now(timezone.utc).isoformat()
markdown = "# Daily News Briefing\n\n"
markdown += f"Generated at: {generated_at}\n\n"
markdown += "\n\n".join(sections)
return markdown, collected
if __name__ == "__main__":
markdown, data = build_briefing()
print(markdown)
Run it:
python briefing.py
The result is intentionally plain. A useful first briefing is a source-linked packet, not a polished report that hides where the information came from.
Optional: save JSON for an agent
If another service will consume the same result, save the normalized articles as JSON:
import json
from pathlib import Path
def save_json(data, path="briefing.json"):
Path(path).write_text(json.dumps(data, indent=2), encoding="utf-8")
Then update the main block:
if __name__ == "__main__":
markdown, data = build_briefing()
print(markdown)
save_json(data)
That gives you both a human-readable briefing and a structured object for a dashboard, queue, or agent tool call.
Optional: pass selected articles to an LLM
When you add a model, keep the prompt strict:
Use only the news items below.
Keep source links attached to every important claim.
Do not invent facts.
If the articles do not support a conclusion, say so.
Pass only the fields the model needs:
def llm_context(data):
lines = []
for section, articles in data.items():
lines.append(f"## {section}")
for article in articles:
lines.append(f"- Title: {article['title']}")
lines.append(f" URL: {article['url']}")
lines.append(f" Published: {article['published']}")
lines.append(f" Description: {article['description']}")
return "\n".join(lines)
Currents is still the news data source. The model is a formatting and reasoning layer that you control.
Complete script
Here is the same example as one file:
import json
import os
from datetime import datetime, timezone
from pathlib import Path
import requests
API_KEY = os.environ["CURRENTS_API_KEY"]
SEARCH_URL = "https://api.currentsapi.services/v1/search"
WATCHLIST = {
"Company developments": "grid battery supplier",
"Policy and regulation": "energy storage regulation",
"Customer and project news": "utility scale battery project",
}
def search_news(keywords, *, language="en", page_size=5):
response = requests.get(
SEARCH_URL,
params={
"keywords": keywords,
"language": language,
"page_number": 1,
"page_size": page_size,
"apiKey": API_KEY,
},
timeout=20,
)
response.raise_for_status()
data = response.json()
if data.get("status") != "ok":
raise RuntimeError(data)
return data.get("news", [])
def normalize_article(article):
return {
"title": article.get("title") or "Untitled",
"description": article.get("description") or "",
"url": article.get("url") or "",
"published": article.get("published") or "",
"language": article.get("language") or "",
"category": article.get("category") or [],
}
def format_article(article):
title = article["title"]
url = article["url"]
published = article["published"]
description = article["description"]
if url:
lines = [f"- {title} - <{url}>"]
else:
lines = [f"- {title}"]
if published:
lines.append(f" - Published: {published}")
if description:
lines.append(f" - {description}")
return "\n".join(lines)
def build_briefing():
sections = []
collected = {}
for section, query in WATCHLIST.items():
articles = [normalize_article(item) for item in search_news(query)]
collected[section] = articles
lines = [f"## {section}", ""]
if not articles:
lines.append("No matching articles found.")
for article in articles:
lines.append(format_article(article))
sections.append("\n".join(lines))
generated_at = datetime.now(timezone.utc).isoformat()
markdown = "# Daily News Briefing\n\n"
markdown += f"Generated at: {generated_at}\n\n"
markdown += "\n\n".join(sections)
return markdown, collected
def save_json(data, path="briefing.json"):
Path(path).write_text(json.dumps(data, indent=2), encoding="utf-8")
if __name__ == "__main__":
markdown, data = build_briefing()
print(markdown)
save_json(data)
Where this fits
This pattern works well when the user already knows what they want to monitor:
- Executive briefings for competitors, customers, suppliers, market narratives, or regulation
- Analyst watchlists for companies, sectors, litigation, policy, and macro topics
- PR and reputation monitoring for brands, executives, campaigns, and incidents
- Consultant or agency client briefs with recurring, source-linked article packets
- Internal agents that need fresh context before answering a question
It is not a trading signal, an investment recommendation, a built-in alerting system, or a license to republish full publisher content. Treat the output as source-linked context for your own workflow.
Production notes
Before turning the script into a scheduled service, decide how you will handle:
- Rate limits and plan quotas
- Retry behavior for network failures
- Duplicate articles across overlapping searches
- Caching and storage windows
- Date ranges for incremental polling
- Publisher rights and attribution requirements
- Human review for high-stakes decisions
Start with one watchlist and a small page size. Once the briefing is useful, expand the number of searches and decide which parts deserve storage, alerts, or model processing.
Next steps
Use Currents Search API when your app starts from a topic, company, domain, category, or date window. Use Latest News API when you need a broader stream of newly indexed articles.
To continue:
- Create a Currents account
- Read the Search API docs
- Review Python examples
- Compare pricing and quotas
The important part is to keep the retrieval layer honest. Collect current articles, preserve source links and published times, and only then let your agent, dashboard, or briefing workflow decide what to do with them.