Synthient.Client
1.0.0
dotnet add package Synthient.Client --version 1.0.0
NuGet\Install-Package Synthient.Client -Version 1.0.0
<PackageReference Include="Synthient.Client" Version="1.0.0" />
<PackageVersion Include="Synthient.Client" Version="1.0.0" />
<PackageReference Include="Synthient.Client" />
paket add Synthient.Client --version 1.0.0
#r "nuget: Synthient.Client, 1.0.0"
#:package Synthient.Client@1.0.0
#addin nuget:?package=Synthient.Client&version=1.0.0
#tool nuget:?package=Synthient.Client&version=1.0.0
Documentation · NuGet · Support
synthient-dotnet
synthient-dotnet is the official .NET SDK for the Synthient API: IP and domain intelligence, account details, and real-time and bulk data feeds.
Table of Contents
SDK Installation
.NET CLI
dotnet add package Synthient.Client
The gRPC schema fetcher is a separate package:
dotnet add package Synthient.Client.GrpcSchema
PackageReference
The second reference is only needed for the gRPC schema fetcher:
<ItemGroup>
<PackageReference Include="Synthient.Client" Version="1.0.0" />
<PackageReference Include="Synthient.Client.GrpcSchema" Version="1.0.0" />
</ItemGroup>
Requirements
.NET 8 or later. Synthient.Client targets net8.0 and net10.0, depends only on System.Text.Json on net8.0, and supports trimming and Native AOT.
API Keys
Every request needs a Synthient API key. Keys are issued from the Synthient dashboard; if you don't have one, contact us. A key's scopes decide what it can call: BASIC covers the lookups and GetAccountAsync, and each feed has its own scopes, listed under Feeds.
If a request fails even though you are sending a key:
- 401 Unauthorized: the key is malformed, unknown, or revoked.
- 402 Payment Required: the team is out of lookup credits.
- 403 Forbidden: the key lacks the scope for that endpoint or feed.
GetAccountAsync shows the key's scopes and the team's remaining credits.
Authentication
This SDK supports the following security scheme:
| Name | Type | Scheme | Environment Variable |
|---|---|---|---|
ApiKey |
API key | x-api-key header |
SYNTHIENT_API_KEY |
The parameterless constructor reads the key from SYNTHIENT_API_KEY:
using var client = new SynthientClient();
To pass the key yourself, such as from a secret manager, use the constructor that takes it, or set ApiKey on SynthientClientOptions:
using var client = new SynthientClient(apiKey);
The constructor makes no network calls. To check a key, call GetAccountAsync, which costs no credits.
SDK Example Usage
Look up an IP address
Get the network, location, and risk intelligence for an IPv4 or IPv6 address. Each lookup costs one credit; failed lookups are free.
using System.Net;
using Synthient;
using var client = new SynthientClient();
var info = await client.LookupIPAsync(IPAddress.Parse("8.8.8.8"));
if (info.Intelligence is { } intelligence)
{
Console.WriteLine($"{intelligence.RiskScore} {string.Join(", ", intelligence.Categories)}");
}
Network, Location, and Intelligence are null when the API has no data for them. A client is safe for concurrent use and pools its connections, so reuse one for the life of your application and dispose it when you are done. The examples below reuse client, and runnable programs are in examples.
Look up many IP addresses
Look up as many as 1000 addresses in one request for ceil(n * 0.9) credits. Private and reserved addresses are skipped and duplicates are merged, so match results by IP.
var results = await client.LookupIPsAsync([IPAddress.Parse("8.8.8.8"), IPAddress.Parse("1.1.1.1")]);
foreach (var result in results)
{
if (result.Intelligence is { } intelligence)
{
Console.WriteLine($"{result.IP} {intelligence.RiskScore}");
}
}
Look up a domain
Get the honeypot traffic observed for a domain and its most recent events. Each lookup costs one credit.
var domain = await client.LookupDomainAsync("example.com", limit: 10);
Console.WriteLine($"{domain.Status} {domain.Stats.Events24h}");
foreach (var recent in domain.RecentEvents)
{
Console.WriteLine($"{recent.Timestamp:u} {recent.TargetSubdomain} {recent.Port}");
}
Check your account
Get the key's account, scopes, and remaining lookup credits, at no cost.
var account = await client.GetAccountAsync();
Console.WriteLine($"{account.Organization.Name} {string.Join(", ", account.Scopes)} {account.LookupQuota.Credits}");
Available Resources and Operations
Every public type and member has XML documentation, which your editor shows as you type.
SynthientClient
LookupIPAsync- Look up an IPv4 or IPv6 addressLookupIPsAsync- Look up as many as 1000 addresses in one requestLookupDomainAsync- Look up the honeypot traffic observed for a domainGetAccountAsync- Get the key's account, scopes, and remaining lookup creditsCheckHealthAsync- Check that the API and its dependencies are healthy
Feeds
Each feed is a property of the client, such as client.Proxies, and has the same methods:
StreamAsync- Stream the feed's real-time eventsListExportsAsync- List one page of the feed's Parquet exports, newest firstListAllExportsAsync- Enumerate every export, fetching pages as neededGetExportMetaAsync- Get an export's checksum, size, row count, and Parquet schemaDownloadAsync- Download an export's Parquet fileGetDownloadUrlAsync- Get a presigned URL for an export's Parquet file, valid for 24 hours
| Property | Data | Event type | Stream scope | Export scope |
|---|---|---|---|---|
Proxies |
Residential, mobile, and datacenter proxies | ProxyEvent |
PROXY_FIREHOSE |
PROXY_FEEDS |
Anonymizers |
VPN and anonymizer IP ranges | AnonymizerEvent |
ANONYMIZERS_STREAM |
ANONYMIZERS_FEED |
Torrents |
BitTorrent DHT and tracker peers | TorrentEvent |
TORRENTS_STREAM |
TORRENTS_FEED |
Ja4t |
JA4T TCP fingerprints | Ja4tEvent |
JA4T_STREAM |
JA4T_FEED |
HeliosHttp |
Helios honeypot HTTP requests | HeliosHttpEvent |
HONEYPOT_HTTP_STREAM |
HONEYPOT_HTTP_FEED |
HeliosHttps |
Helios honeypot TLS ClientHellos | HeliosHttpsEvent |
HONEYPOT_HTTPS_STREAM |
HONEYPOT_HTTPS_FEED |
HeliosDns |
Helios honeypot tunnel destinations | HeliosDnsEvent |
HONEYPOT_DNS_STREAM |
HONEYPOT_DNS_FEED |
HeliosAdb |
Helios honeypot Android Debug Bridge commands | HeliosAdbEvent |
HONEYPOT_ADB_STREAM |
HONEYPOT_ADB_FEED |
Streaming
StreamAsync returns an IAsyncEnumerable of a feed's real-time events. Each await foreach opens a connection, and leaving the loop closes it:
while (!cancellationToken.IsCancellationRequested)
{
try
{
await foreach (var proxy in client.Proxies.StreamAsync(cancellationToken))
{
Console.WriteLine($"{proxy.IP} {proxy.Provider} {proxy.Type}");
}
}
catch (SynthientException e) when (e is not ApiException { StatusCode: 401 or 403 })
{
Console.Error.WriteLine($"proxy stream: {e.Message}");
// Avoid reconnecting in a tight loop.
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
}
}
- The server closes each stream after up to 30 minutes. That ends the
await foreachwithout an exception, and the outer loop reconnects at once. - A 401 or 403 isn't caught, so it ends the loop, because reconnecting can't fix a rejected key or a missing scope. Cancelling the token ends it with an
OperationCanceledException. - Any other failure throws a
SynthientException, and the loop waits 2 seconds before reconnecting. - Events sent while disconnected are missed. The API has no resume cursor.
Pagination
ListExportsAsync returns one page of a feed's exports, newest first. Pass page.NextCursor as cursor to get the next page; it is null or empty on the last one.
var page = await client.Proxies.ListExportsAsync(limit: 50);
foreach (var export in page.Exports)
{
Console.WriteLine($"{export.Id} {export.Kind} {export.RowCount}");
}
ListAllExportsAsync fetches the pages for you as the loop needs them:
await foreach (var export in client.Proxies.ListAllExportsAsync())
{
Console.WriteLine($"{export.Id} {export.Kind} {export.RowCount}");
}
On the first page, the newest hourly export has the ID latest, a moving alias for the freshest export. Pass its Date and Hour to ExportId.Hourly for its stable ID, such as 2026-10-14/21.
Downloads
Exports are Parquet files. DownloadAsync returns a stream of a file's contents, which you must dispose:
await using var body = await client.Proxies.DownloadAsync(ExportId.Latest);
await using var file = File.Create("proxies.parquet");
await body.CopyToAsync(file);
- Name an export by its
Idfrom a listing, byExportId.Latest, or byExportId.Daily(day)orExportId.Hourly(day, hour)for a UTC day or hour. - The token passed to
DownloadAsynccovers only opening the file. To bound the rest, pass a token to each read, such asbody.CopyToAsync(file, cancellationToken). GetExportMetaAsyncreturns the file's SHA-256 checksum. Use a fixed ID for both calls, becauselatestcan move between them.GetDownloadUrlAsyncreturns a presigned URL that works without a key for 24 hours, so treat it as a secret. Your API key is never sent to the storage host.- Hourly exports roll into the daily export shortly after 00:30 UTC the next day. After that, the hour returns a 400.
Retries
The client retries a failed request up to twice, backing off exponentially with jitter and honoring Retry-After:
| Requests | Retried on |
|---|---|
| Account and feed requests | 500, 502, 503, and 504, most connection failures and timeouts, and 429 with Retry-After |
Billed lookups and CheckHealthAsync |
Only 429 with Retry-After, which comes before any work, so a lookup is never charged twice |
A 429 without Retry-After means the monthly request budget is spent or too many streams are open, so it is thrown at once. Downloads from the storage host are never retried.
Set the number of retries with MaxRetries. Zero disables them:
using var client = new SynthientClient(new SynthientClientOptions { MaxRetries = 4 });
Timeouts
Timeout bounds each wait on the network, such as for response headers or the next read of a stream, but not a whole call, so streams and downloads are not cut off. It defaults to 60 seconds, and Timeout.InfiniteTimeSpan disables it:
using var client = new SynthientClient(new SynthientClientOptions { Timeout = TimeSpan.FromSeconds(30) });
When it elapses, the call throws ApiTimeoutException, so a stream that sends nothing for that long fails. The default handler also gives up connecting after 10 seconds.
To bound a whole call, pass a CancellationToken. Cancelling it throws OperationCanceledException:
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var info = await client.LookupIPAsync(IPAddress.Parse("8.8.8.8"), cancellation.Token);
Error Handling
A request that fails with an error status throws an ApiException:
| Property | Type | Description |
|---|---|---|
StatusCode |
int |
HTTP status code, such as 404 |
Type |
string? |
URI identifying the problem type |
Title |
string? |
Short summary of the problem type |
Detail |
string? |
Explanation of this occurrence |
Errors |
IReadOnlyDictionary<string, IReadOnlyList<string>> |
Validation messages by request field |
RequestId |
string? |
Request ID to give Synthient support |
RetryAfter |
TimeSpan? |
How long the server asked you to wait, or null |
Headers |
IReadOnlyDictionary<string, IReadOnlyList<string>> |
Response headers, with case-insensitive names |
Message holds only the status, such as 403 Forbidden, so it is safe to log. The properties hold untrusted server data.
Example
try
{
await client.LookupIPAsync(IPAddress.Parse("8.8.8.8"));
}
catch (ApiException e) when (e.StatusCode == 402)
{
Console.WriteLine("out of lookup credits");
}
catch (ApiException e) when (e.StatusCode == 429 && e.RetryAfter is { } retryAfter)
{
Console.WriteLine($"rate limited, retry after {retryAfter}");
}
catch (ApiException e) when (e.StatusCode == 429)
{
Console.WriteLine("monthly request budget spent");
}
catch (ApiException e)
{
Console.WriteLine($"request {e.RequestId} failed: {e.Detail ?? e.Title}");
}
Error Types
SynthientException: the base type of the SDK's exceptions, also thrown for a response the SDK can't decode.ApiException: the API, or the storage host duringDownloadAsync, returned an error status.ApiConnectionException: no response arrived, or reading the response failed.ApiTimeoutException: anApiConnectionExceptionthrown whenTimeoutor the 10-second connect timeout elapses.OperationCanceledException: the call'sCancellationTokenwas cancelled.ArgumentException: a missing API key or another invalid argument or option, rejected before any request is sent.ObjectDisposedException: the client was disposed.
Status codes
| Status | Meaning |
|---|---|
| 400 | The request is invalid, such as an hourly export that was rolled up |
| 401 | The API key is malformed, unknown, or revoked |
| 402 | The team is out of lookup credits |
| 403 | The key lacks the scope for the endpoint or feed |
| 404 | The export doesn't exist, or a daily export is still rolling up (RetryAfter is set) |
| 422 | The input is invalid, such as a private address or a malformed domain |
| 429 | Rate limited if RetryAfter is set; otherwise the budget is spent or too many streams are open |
| 5xx | The server failed |
Server Selection
Override Server URL Per-Client
The default server is https://api.synthient.com/api/v4. Set BaseAddress to send requests elsewhere, such as a mock server in tests:
using var client = new SynthientClient(new SynthientClientOptions
{
BaseAddress = new Uri("http://localhost:8080/api/v4"),
});
Custom HTTP Client
The client takes an HttpMessageHandler, not an HttpClient. Pass your own as Handler, for example to use a proxy:
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy("http://proxy.example.com:8080"),
AllowAutoRedirect = false,
MaxResponseDrainSize = 0,
};
using var client = new SynthientClient(new SynthientClientOptions { Handler = handler });
- Turn off
AllowAutoRedirect, because a followed export redirect would send the API key to the storage host. A handler with it set is rejected withArgumentException. - The handler must not retry requests, so a billed lookup is never sent twice.
- Set
MaxResponseDrainSizeto 0 so that leaving a stream or download early closes its connection. - The client never disposes your handler.
Set UserAgent = "my-app/1.2" to add your product token to the User-Agent header.
Debugging
ResponseHook is called after every API response, including errors, retries, pages, and stream handshakes, with the status, request ID, rate limit, and headers:
using var client = new SynthientClient(new SynthientClientOptions
{
ResponseHook = metadata =>
{
Console.WriteLine($"status={metadata.StatusCode} request_id={metadata.RequestId}");
if (metadata.RateLimit is { } rateLimit)
{
Console.WriteLine($"rate limit: {rateLimit.Remaining} of {rateLimit.Limit} left");
}
},
});
The hook must be safe to call concurrently. If it throws, the call fails with its exception.
Response headers can contain secrets, such as presigned download URLs. Avoid logging metadata.Headers in production.
gRPC Schema
The Synthient.Client.GrpcSchema package fetches the protobuf schema of the Synthient gRPC API through server reflection:
try
{
var schema = await GrpcSchema.FetchAsync(Environment.GetEnvironmentVariable("SYNTHIENT_API_KEY"));
foreach (var file in schema.DescriptorSet.File)
{
Console.WriteLine($"{file.Name} {file.Package}");
}
}
catch (GrpcSchemaException e)
{
var explanation = GrpcSchema.Explain(e);
Console.Error.WriteLine(explanation is null ? e.Message : $"{explanation} {e.Message}");
}
schema.DescriptorSet is a Google.Protobuf.Reflection.FileDescriptorSet. GrpcSchema.Explain describes common gRPC errors in plain language.
Development
Maturity
This SDK follows Semantic Versioning. Breaking changes ship only in a new major version. Synthient.Client and Synthient.Client.GrpcSchema are released together with the same version.
Contributions
Bug reports and feature requests are welcome as GitHub issues. For questions about the API or your account, contact Synthient support.
License
| 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 is compatible. 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. |
-
net10.0
- No dependencies.
-
net8.0
- System.Text.Json (>= 10.0.12)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Synthient.Client:
| Package | Downloads |
|---|---|
|
Synthient.Client.GrpcSchema
Fetches the protobuf schema of the Synthient gRPC API using gRPC server reflection. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 45 | 10/20/2026 |