newsdataapi 0.2.0
newsdataapi: ^0.2.0 copied to clipboard
Official Dart/Flutter client for the Newsdata.io News API: real-time, historical, crypto & market news with retries, pagination and typed errors.
Official Dart/Flutter client for the Newsdata.io News
API. Pure Dart — works in Flutter (iOS, Android, Web, Desktop) and standalone
Dart (CLI, server, AngularDart). Typed named-parameter methods for all eight
endpoints with client-side validation, retries with exponential backoff,
Stream-based pagination, and a typed exception hierarchy.
Zero Flutter dependencies; only runtime deps are package:http and
package:meta.
Installation #
flutter pub add newsdataapi
# or for pure Dart projects:
dart pub add newsdataapi
Quickstart #
import 'package:newsdataapi/newsdataapi.dart';
Future<void> main() async {
final client = NewsDataApiClient(apiKey: 'YOUR_API_KEY');
try {
final resp = await client.latest(
q: 'bitcoin',
country: ['us', 'gb'],
language: ['en'],
);
for (final article in resp.articles) {
print('${article.title} — ${article.link}');
}
} on NewsdataRateLimitException catch (e) {
print('rate limited, retry after ${e.retryAfter} seconds');
} on NewsdataException catch (e) {
print('failed: $e');
} finally {
client.close();
}
}
Endpoints #
| Method | Endpoint | Notes |
|---|---|---|
client.latest(...) |
/1/latest |
Real-time news |
client.archive(...) |
/1/archive |
Historical news |
client.sources(...) |
/1/sources |
Available sources (single page) |
client.crypto(...) |
/1/crypto |
Cryptocurrency news |
client.market(...) |
/1/market |
Market / financial news |
client.count({required fromDate, required toDate, ...}) |
/1/count |
Aggregate counts |
client.cryptoCount({required fromDate, required toDate, ...}) |
/1/crypto/count |
Aggregate crypto counts |
client.marketCount({required fromDate, required toDate, ...}) |
/1/market/count |
Aggregate market counts |
client.websocketRegister(...) |
/1/websocket/register |
Register a real-time query |
client.websocketFetch() |
/1/websocket/fetch |
List registered queries |
client.websocketDelete(id) |
/1/websocket/delete |
Delete a registered query |
Every endpoint method has typed named parameters for every accepted field —
the Dart compiler enforces that only valid parameters for that endpoint
compile. The count endpoints' fromDate / toDate are required, so
forgetting them is a compile error, not a runtime one.
Multi-value params accept List<String> (sent comma-joined). Booleans become
1/0. Names like qInTitle/fromDate are camelCase Dart-side — the SDK
maps them to the API's lowercase form (qintitle, from_date).
Pagination #
Two helpers — both work for every endpoint except sources:
// 1) scrollAll: follow nextPage and return one merged response.
final all = await client.scrollAll(
endpoint: Endpoint.latest,
params: {'q': 'news'},
maxResults: 200,
);
print(all.articles.length); // up to 200 articles
// 2) paginate: idiomatic Stream<NewsdataResponse>.
await for (final page in client.paginate(
endpoint: Endpoint.latest,
params: {'q': 'news'},
maxPages: 5,
)) {
process(page.articles);
}
paginate returns a Stream, so you can use everything Dart streams give
you — take, where, transform, cancellation via await for break.
Raw query #
await client.latest(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.
Client-side validation #
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;sentimentScoreis set withoutsentiment.
Plus the count endpoints' fromDate/toDate are required at the type
level — no runtime check needed.
Real-time news (WebSocket) #
Register a query first — the returned registration_id identifies it from then on:
final registered = await client.websocketRegister(q: 'bitcoin', language: ['en']);
final registrationId = registered.aggregate!['registration_id'] as String;
websocketRegister takes the familiar filter parameters (q, country,
language, domain, …) — no date or paging filters, since a registered query
matches news as it is published. Registering an identical query twice throws
NewsdataApiException with statusCode 409; the existing id is in the
response body. websocketFetch() lists every registered query and
websocketDelete(id) removes one.
Then stream — each response has the familiar status / totalResults /
results shape:
final ws = NewsDataApiWebSocket(client);
await for (final response in ws.stream(registrationId)) {
for (final article in response.articles) {
print('${article.title} - ${article.link}');
}
}
Break out of the loop, cancel the subscription, or call ws.close() to stop;
the connection is closed either way.
Transient drops (network errors, server restarts, abnormal closes) are
reconnected automatically with a capped exponential backoff. Pass
reconnect: false to stop on the first disconnect instead. A permanent
rejection — bad API key or unknown
registration_id, exhausted API credits, or too many simultaneous devices — throws
NewsdataWebSocketAuthException and is not retried.
The server always accepts the handshake and then closes with code 1008 when
the connection is refused, carrying one of three reasons: invalid credentials or registration not found, api limit reached, or device limit reached (more
than 5 devices on one registration_id). Every other close code — including
1013 (send timeout, meaning the client read too slowly) — is transient and
reconnects.
Each delivered article consumes 1 API credit per connected device.
Catch it like any other client error:
try {
await for (final response in ws.stream(registrationId)) {
// ...
}
} on NewsdataWebSocketAuthException catch (e) {
print('rejected: ${e.message}');
} on NewsdataWebSocketException catch (e) {
print('stream error: ${e.message}');
}
All connection options are optional:
final ws = NewsDataApiWebSocket(
client,
baseUrl: 'wss://ws.newsdata.io/ws/event', // staging / self-hosted
reconnect: true, // default true
reconnectDelay: const Duration(seconds: 1), // first delay; doubles each retry
reconnectDelayMax: const Duration(seconds: 30), // cap on the delay
handshakeTimeout: const Duration(seconds: 10), // opening handshake bound
);
Streaming uses web_socket_channel,
so it works on native and web targets.
Error handling #
try {
final resp = await client.latest(q: 'news');
} on NewsdataValidationException catch (e) {
// bad parameter — e.param, e.message
} on NewsdataAuthException catch (e) {
// 401 / 403
} on NewsdataRateLimitException catch (e) {
// 429 — e.retryAfter (seconds)
} on NewsdataServerException catch (e) {
// 5xx
} on NewsdataApiException catch (e) {
// other API errors — e.statusCode, e.responseBody
} on NewsdataNetworkException catch (e) {
// socket / TLS / timeout — e.cause
}
Hierarchy:
NewsdataException (catch-all base)
├── NewsdataValidationException (.param, .message)
├── NewsdataApiException (.statusCode, .responseBody)
│ ├── NewsdataAuthException (401 / 403)
│ ├── NewsdataRateLimitException (429; .retryAfter)
│ └── NewsdataServerException (5xx)
├── NewsdataNetworkException (.cause)
└── NewsdataWebSocketException (.cause — real-time stream)
└── NewsdataWebSocketAuthException (policy-violation close 1008)
Configuration #
final client = NewsDataApiClient(
apiKey: apiKey,
timeout: const Duration(seconds: 30),
maxRetries: 5,
retryBackoff: const Duration(seconds: 2), // base, exponential
retryBackoffMax: const Duration(seconds: 60), // cap on a single sleep
paginationDelay: const Duration(seconds: 1),
includeHeaders: true, // attach response headers
httpClient: myCustomClient, // proxies, mTLS, etc.
logger: (level, msg) => print('[$level] $msg'),// API key is redacted in URLs
);
httpClient is the standard http.Client — inject http_test_handler,
http_logger, dio_http_client, or any package that provides one, and the
SDK uses it transparently. The SDK does not close the injected client (the
caller owns it); it does close a client it created itself when client.close()
is called.
Retries cover network errors, HTTP 429, and 5xx responses. 429 honors the
Retry-After header (integer seconds or HTTP-date); otherwise backoff is
exponential. Auth and other 4xx errors are never retried.
Where it works #
| Platform | Supported |
|---|---|
| Flutter — iOS | ✅ |
| Flutter — Android | ✅ |
| Flutter — Web | ✅ (CORS rules on the API apply) |
| Flutter — macOS / Windows / Linux | ✅ |
| Pure Dart — CLI / server | ✅ |
This is a pure Dart package, not a plugin — no platform channels, no Swift or Kotlin to compile.
Development #
dart pub get
dart analyze --fatal-infos
dart format --output=none --set-exit-if-changed lib test example
dart test # 34 tests; no network needed
The test suite uses package:http/testing's MockClient to mock the API
end-to-end. No API key required.
Related libraries #
Official Newsdata.io clients across languages and runtimes:
- Python — newsdataapi/python-client (PyPI)
- Node.js — newsdataapi/newsdata-nodejs-client (npm)
- React (hooks) — newsdataapi/newsdata-reactjs-client (npm)
- PHP — newsdataapi/php-client (Packagist)
- Java — newsdataapi/newsdata-java-sdk (Maven Central)
- .NET — newsdataapi/newsdata-dotnet-sdk (NuGet)
- Go — newsdataapi/newsdata-go-client (pkg.go.dev)
- MCP Server (AI assistants) — newsdataapi/newsdata.io-mcp (PyPI)
Also see free news datasets for ML / NLP work.
