Finlight.Client
1.0.1
dotnet add package Finlight.Client --version 1.0.1
NuGet\Install-Package Finlight.Client -Version 1.0.1
<PackageReference Include="Finlight.Client" Version="1.0.1" />
<PackageVersion Include="Finlight.Client" Version="1.0.1" />
<PackageReference Include="Finlight.Client" />
paket add Finlight.Client --version 1.0.1
#r "nuget: Finlight.Client, 1.0.1"
#:package Finlight.Client@1.0.1
#addin nuget:?package=Finlight.Client&version=1.0.1
#tool nuget:?package=Finlight.Client&version=1.0.1
finlight .NET Client
Official .NET client for the finlight.me financial news API. Full API documentation: docs.finlight.me
Features
- π Article search β full-text and field queries over enriched financial news
- β‘ Real-time streaming β enhanced and raw WebSocket feeds as
IAsyncEnumerable - π Resilient by default β retries with backoff, automatic reconnects, proactive connection rotation
- π Webhook verification β HMAC-SHA256 signature checks with replay protection
- πͺΆ Lightweight β one dependency (
Microsoft.Extensions.Logging.Abstractions), nullable-annotated, fully documented API
Installation
dotnet add package Finlight.Client
Requires .NET 8 or later.
Quick Start
using Finlight;
using var client = new FinlightClient("your-api-key");
var response = await client.Articles.FetchArticlesAsync(new GetArticlesParams
{
Query = "(ticker:AAPL OR ticker:NVDA) AND NOT source:www.reuters.com",
PageSize = 20,
});
foreach (var article in response.Articles)
{
Console.WriteLine($"{article.PublishDate:u} [{article.Sentiment}] {article.Title}");
}
REST API
Search articles
var response = await client.Articles.FetchArticlesAsync(new GetArticlesParams
{
Query = "artificial intelligence",
From = "2024-01-01",
To = "2024-02-01",
Language = "en",
OrderBy = ArticleOrderBy.PublishDate,
Order = SortOrder.Desc,
PageSize = 100,
Page = 1,
IncludeContent = true,
IncludeEntities = true,
Categories = [Category.Markets, Category.Technology],
});
The API reports no total count; advance Page until a short or empty page comes back.
Fetch a single article by URL
var article = await client.Articles.FetchArticleByLinkAsync(new GetArticleByLinkParams
{
Link = "https://www.example.com/some-article",
IncludeContent = true,
});
List sources
var sources = await client.Sources.GetSourcesAsync();
WebSocket Streaming
Enhanced stream
Enriched articles (sentiment, entities, content). Duplicates within the last 10 deliveries are suppressed.
await foreach (var article in client.WebSocket.StreamAsync(new GetArticlesWebSocketParams
{
Query = "ticker:NVDA",
IncludeContent = true,
}, cancellationToken))
{
Console.WriteLine($"{article.Source}: {article.Title}");
}
Raw stream
Unenriched articles with lower latency. The query language is limited to
source:, title:, and summary: fields.
await foreach (var article in client.RawWebSocket.StreamAsync(new GetRawArticlesWebSocketParams
{
Query = "title:earnings",
}, cancellationToken))
{
Console.WriteLine($"{article.Source}: {article.Title}");
}
Streaming semantics
- Reconnects (exponential backoff from 500ms to 10s, rate-limit waits, proactive rotation before the server's 2-hour connection cap) are handled internally.
- The stream ends normally when the server preempts this client because
another connection took over the slot (see
Takeover). FinlightBlockedExceptionis thrown when the server permanently rejects the connection β reconnecting will not help.- Cancel the token or
breakout of the loop to stop; breaking closes the connection cleanly. - One active stream per client instance; a concurrent second stream throws
InvalidOperationException.
Custom WebSocket options
using Finlight.WebSockets;
var ws = new ArticleWebSocketClient(
new FinlightClientOptions { ApiKey = "your-api-key" },
new FinlightWebSocketOptions
{
Takeover = true, // take over the connection slot from another client
OnClose = (code, reason) => Console.WriteLine($"closed: {code} {reason}"),
});
| Option | Default | Description |
|---|---|---|
PingInterval |
25s | Application-level ping cadence |
PongTimeout |
60s | Force reconnect when no pong arrives |
BaseReconnectDelay |
500ms | First reconnect backoff |
MaxReconnectDelay |
10s | Backoff cap |
ConnectionLifetime |
115min | Proactive rotation, under the 2h server cap |
Takeover |
false |
Take over an existing connection for the same key |
OnClose |
β | Callback invoked with (closeCode, reason) |
Webhooks
Verify inbound webhooks with the raw, unmodified request body:
app.MapPost("/webhooks/finlight", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var rawBody = await reader.ReadToEndAsync();
try
{
var article = FinlightWebhooks.ConstructEvent(
rawBody,
request.Headers["X-Webhook-Signature"]!,
endpointSecret: "your-webhook-secret",
request.Headers["X-Webhook-Timestamp"]);
// handle article ...
return Results.Ok();
}
catch (FinlightWebhookVerificationException)
{
return Results.Unauthorized();
}
});
The signature is an HMAC-SHA256 over "{timestamp}.{body}" (or the body alone
when no timestamp header is present), compared in constant time, with a
5-minute replay tolerance.
Configuration
| Option | Default | Description |
|---|---|---|
ApiKey |
β (required) | Your finlight API key |
BaseUrl |
https://api.finlight.me |
REST base URL |
WssUrl |
wss://wss.finlight.me |
WebSocket base URL |
Timeout |
5s | Per-attempt timeout (REST and WebSocket handshake) |
RetryCount |
3 | Total REST attempts |
TimeProvider |
system | Clock override for tests |
Dependency injection
The client works with IHttpClientFactory and never disposes a caller-owned
HttpClient:
services.AddHttpClient("finlight", http => http.Timeout = Timeout.InfiniteTimeSpan);
services.AddSingleton(sp => new FinlightClient(
new FinlightClientOptions { ApiKey = configuration["Finlight:ApiKey"]! },
sp.GetRequiredService<IHttpClientFactory>().CreateClient("finlight"),
sp.GetService<ILoggerFactory>()));
Logging
Pass an ILoggerFactory to get structured logs (connection lifecycle, retries,
protocol events). Without one, the client is silent.
Error Handling
| Exception | Meaning |
|---|---|
FinlightApiException |
Non-2xx REST response after retries; carries StatusCode, ReasonPhrase, Body |
FinlightBlockedException |
WebSocket permanently rejected (close code 1008) |
FinlightWebhookVerificationException |
Webhook signature/timestamp/payload validation failed |
TimeoutException |
A single REST attempt exceeded Timeout |
Retries: statuses 429, 500, 502, 503, and 504 are retried up to RetryCount
total attempts with exponential backoff (500ms Β· 2^(attemptβ1)).
Testing
dotnet test # unit tests (offline)
FINLIGHT_API_KEY=... dotnet test # + integration tests against the live API
License
Support
- π Documentation
- π§ info@finlight.me
- π GitHub Issues
- π finlight.me
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.