An async Rust client for the newsdata.io news API.
Built on tokio and reqwest,
it gives you strongly-typed responses, ergonomic query builders, and a reusable
connection-pooled HTTP client. It is designed to run out of the box on the
free newsdata.io plan.
- 🚀 Fully async (Tokio) with a pooled
reqwestclient under the hood - 🧱 Typed responses (
NewsResponse,Article,Source, ...) viaserde - 🔧 Fluent
NewsQuerybuilder forq,country,language,category, and pagination - 🆓 Free-tier safe — paid-only fields are optional and paid-only errors degrade gracefully
- 🛡️ Rich error handling for invalid keys (401), rate limits (429), and upgrade-required (403/422)
- 🔌 Connection pooling example for high-throughput, concurrent usage
This repository is the crate. Clone it and use it as a path/git dependency, or copy it into your workspace:
[dependencies]
newsdata-client = { git = "https://github.com/your-account/newsdata-rust-client" }
tokio = { version = "1", features = ["full"] }Requires Rust 1.75+.
- Create a free account at https://newsdata.io.
- Copy your API key from the dashboard.
- Export it as an environment variable (the client never hardcodes it):
export NEWSDATA_API_KEY="pub_xxxxxxxxxxxxxxxxxxxxxxxx"If NEWSDATA_API_KEY is unset, NewsDataClient::from_env() returns
Error::MissingApiKey with a clear message instead of panicking.
use newsdata_client::{NewsDataClient, NewsQuery};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = NewsDataClient::from_env()?;
let query = NewsQuery::new()
.query("technology")
.language("en")
.country("us");
let page = client.latest_news(&query).await?;
for article in &page.results {
println!("{}", article.title);
}
// Pagination: pass the returned token back via `.page(...)`.
if let Some(token) = page.next_page {
let next = client.latest_news(&query.clone().page(token)).await?;
println!("next page has {} articles", next.results.len());
}
Ok(())
}Run any example with your key set:
NEWSDATA_API_KEY=your_key cargo run --example latest_news
NEWSDATA_API_KEY=your_key cargo run --example sources
NEWSDATA_API_KEY=your_key cargo run --example connection_poolinglatest_news— fetch and print the latest articlessources— list available publishers for a countryconnection_pooling— share one pooled client across many concurrent Tokio tasks
reqwest::Client keeps an internal connection pool, so the fast path is to
build it once and share it. NewsDataClient is cheap to clone and clones
share the same pool. You can also bring your own tuned reqwest::Client:
use std::time::Duration;
use newsdata_client::NewsDataClient;
let http = reqwest::Client::builder()
.pool_max_idle_per_host(16)
.pool_idle_timeout(Duration::from_secs(90))
.timeout(Duration::from_secs(30))
.build()?;
let client = NewsDataClient::with_http_client(
std::env::var("NEWSDATA_API_KEY")?,
http,
);
# Ok::<(), Box<dyn std::error::Error>>(())All API calls return Result<_, newsdata_client::Error>:
| Variant | Meaning |
|---|---|
MissingApiKey |
NEWSDATA_API_KEY not set |
Unauthorized |
API key rejected (HTTP 401) |
RateLimited |
Free-tier rate limit hit (HTTP 429) — back off and retry |
PaidFeature(msg) |
Request needs a paid plan (HTTP 403/422 "upgrade" response) |
Api { status, message } |
Any other API error |
Http / Decode |
Transport or JSON decoding failure |
Empty results are handled normally (you get an empty Vec, not an error).
This crate targets the free plan. The following are paid-only on newsdata.io and are intentionally treated as optional:
- Sentiment analysis —
Article::sentimentisOptionandNoneon free. - AI enrichment fields —
ai_tag,ai_region,ai_orgare optional. - The
/archiveendpoint and long historical date ranges. - Advanced full-text query operators.
If you request a paid-only feature with a free key, the API responds with an
"upgrade your plan" error, which this client surfaces as Error::PaidFeature
so your app can skip it and keep working rather than crashing.
MIT