Official .NET client (SDK) for the Newsdata.io News
API. Async methods for every endpoint (latest, archive, sources,
crypto, market, count, crypto/count, market/count) with
client-side parameter validation, retries with exponential backoff,
IAsyncEnumerable-based pagination, and a typed exception hierarchy.
Built on .NET 8's HttpClient and System.Text.Json — zero runtime
dependencies. Thread-safe.
dotnet add package Newsdata.ApiOr csproj:
<PackageReference Include="Newsdata.Api" Version="0.0.1" />using Newsdata.Api;
using Newsdata.Api.Exceptions;
using var client = new NewsDataApiClient(
Environment.GetEnvironmentVariable("NEWSDATA_API_KEY")!);
var resp = await client.LatestAsync(Params.Of()
.With("q", "bitcoin")
.With("country", new[] { "us", "gb" })
.With("language", new[] { "en" }));
foreach (var article in resp.GetArticles())
{
Console.WriteLine($"{article.Title} — {article.Link}");
}| Method | Endpoint | Notes |
|---|---|---|
client.LatestAsync(params) |
/1/latest |
Real-time news |
client.ArchiveAsync(params) |
/1/archive |
Historical news |
client.SourcesAsync(params) |
/1/sources |
Available sources (single page) |
client.CryptoAsync(params) |
/1/crypto |
Cryptocurrency news |
client.MarketAsync(params) |
/1/market |
Market / financial news |
client.CountAsync(params) |
/1/count |
Aggregate counts (requires from_date, to_date) |
client.CryptoCountAsync(params) |
/1/crypto/count |
Aggregate crypto counts |
client.MarketCountAsync(params) |
/1/market/count |
Aggregate market counts |
params is IDictionary<string, object?> — pass a Params instance for
fluent construction. Values may be string, int, double, bool, or
IEnumerable<string> (sent comma-joined). Parameter names are
case-insensitive — qInTitle and qintitle are equivalent.
Every method accepts an optional CancellationToken as the last argument.
// 1) ScrollAllAsync: follow nextPage cursors and return one merged response.
var all = await client.ScrollAllAsync(
Endpoint.Latest,
Params.Of().With("q", "news"),
maxResults: 200); // 0 = no cap
// 2) Paginate: idiomatic IAsyncEnumerable.
await foreach (var page in client.Paginate(
Endpoint.Latest, Params.Of().With("q", "news")))
{
foreach (var article in page.GetArticles())
Process(article);
}Use break (or a cancelled CancellationToken) to stop the await foreach
early.
await client.LatestAsync(Params.Of()
.With("rawQuery", "q=bitcoin&country=us&language=en"));rawQuery is mutually exclusive with every other parameter and is parsed
and validated against the endpoint's allowed keys before the request leaves.
A NewsdataValidationException is thrown — before any HTTP request — when:
- a parameter is not accepted by that endpoint;
- mutually-exclusive parameters are set together —
q/qInTitle/qInMeta,country/excludecountry,category/excludecategory,language/excludelanguage,domain/domainurl/excludedomain; sizeis outside 1–50;sentiment_scoreis set withoutsentiment;- a count endpoint is missing
from_dateorto_date.
Booleans for full_content, image, video, and removeduplicate are
coerced to "1"/"0".
using Newsdata.Api.Exceptions;
try
{
await client.LatestAsync(Params.Of().With("q", "news"));
}
catch (NewsdataValidationException e) { /* bad param — e.Param */ }
catch (NewsdataAuthException e) { /* 401 / 403 */ }
catch (NewsdataRateLimitException e) { /* 429 — e.RetryAfter */ }
catch (NewsdataServerException e) { /* 5xx */ }
catch (NewsdataApiException e) { /* other API errors — e.StatusCode */ }
catch (NewsdataNetworkException e) { /* transport — e.InnerException */ }Hierarchy:
NewsdataException (extends Exception)
├── NewsdataValidationException (.Param)
├── NewsdataApiException (.StatusCode, .ResponseBody)
│ ├── NewsdataAuthException (401 / 403)
│ ├── NewsdataRateLimitException (429; .RetryAfter)
│ └── NewsdataServerException (5xx)
└── NewsdataNetworkException (.InnerException)
using var client = new NewsDataApiClient(new NewsDataApiClientOptions
{
ApiKey = apiKey,
Timeout = TimeSpan.FromSeconds(30),
MaxRetries = 5,
RetryBackoff = TimeSpan.FromSeconds(2),
RetryBackoffMax = TimeSpan.FromSeconds(60),
PaginationDelay = TimeSpan.FromSeconds(1),
BaseUrl = "https://staging.example/api/1/",
IncludeHeaders = true,
HttpClient = myInjectedClient, // proxies, mTLS, etc.
Logger = (level, msg) => Console.WriteLine($"{level}: {msg}"),
});Retries cover network errors, HTTP 429, and 5xx. 429 honours the
Retry-After header (integer seconds or HTTP-date); otherwise backoff is
exponential. Auth (401/403) and other 4xx errors are never retried. The
API key is redacted from any URL passed to the logger.
NewsDataApiClient is thread-safe — share one instance across your
application. Dispose it on shutdown (or use using var); the SDK only
disposes the underlying HttpClient if it created it.
dotnet build
dotnet test # 33 tests, no API key needed
dotnet pack -c Release # produces .nupkg + .snupkgTests use a custom HttpMessageHandler mock — no WireMock, no network,
no external test dep.
Tag-push driven. Bump <Version> in src/Newsdata.Api/Newsdata.Api.csproj,
push a v-prefixed tag, and publish.yml does the rest:
git tag -a v0.0.2 -m "v0.0.2"
git push origin v0.0.2See .github/workflows/publish.yml for
the NuGet.org publishing flow.