Feeds
Stream each feed's real-time events and download its hourly and daily Parquet exports.
Each feed is returned by a method of SynthientClient as a Feed<T>, where T is the feed's event type. Every feed has a real-time stream and hourly Parquet exports that roll up into daily exports. Streaming and exports need separate scopes on the API key. The event types are listed under Event types.
| Method | Event type | Stream scope | Export scope |
|---|---|---|---|
proxies() | ProxyEvent | PROXY_FIREHOSE | PROXY_FEEDS |
anonymizers() | AnonymizerEvent | ANONYMIZERS_STREAM | ANONYMIZERS_FEED |
torrents() | TorrentEvent | TORRENTS_STREAM | TORRENTS_FEED |
ja4t() | Ja4tEvent | JA4T_STREAM | JA4T_FEED |
heliosHttp() | HeliosHttpEvent | HONEYPOT_HTTP_STREAM | HONEYPOT_HTTP_FEED |
heliosHttps() | HeliosHttpsEvent | HONEYPOT_HTTPS_STREAM | HONEYPOT_HTTPS_FEED |
heliosDns() | HeliosDnsEvent | HONEYPOT_DNS_STREAM | HONEYPOT_DNS_FEED |
heliosAdb() | HeliosAdbEvent | HONEYPOT_ADB_STREAM | HONEYPOT_ADB_FEED |
What each feed carries:
proxies()carries proxy sightings.anonymizers()carries the address ranges of VPN and anonymizing services.torrents()carries torrents and their peers.ja4t()carries TCP fingerprints seen from proxies.- The four Helios feeds carry what honeypots received through proxies: HTTP requests, TLS handshakes, tunnel destinations, and Android Debug Bridge commands. Despite its name,
heliosDns()carries tunnel destinations, not DNS query names.
See Firehose, Feeds, and Helios for the data behind each feed.
Streams
stream
Connects to the feed's real-time stream and returns it as an open EventStream<T>.
public EventStream<T> stream()
- Connection: The connection is retried like any other feed request (see Retries and timeouts), but events are never replayed once the stream is open.
- Errors: A non-success response throws an
ApiException. A 429 withoutretryAfter()means the key's monthly request budget is spent or it already has too many open streams.
EventStream
An EventStream<T> is an Iterable<T> and a Closeable, so read it with a for-each loop inside try-with-resources.
public Iterator<T> iterator()
public void close()
- Iteration: Reading blocks until the server sends the next event.
iterator()may be called once, and a second call throwsIllegalStateException. - Cleanup:
close()disconnects. It is safe to call more than once and from any thread, and calling it from another thread ends the iteration. - Connection: The server keeps a stream open for up to 30 minutes and then closes it, which ends the iteration normally, without an exception.
- Errors: A lost connection throws
ApiConnectionException, and an event that cannot be decoded throwsSynthientException. Either one ends the stream. - Timeout: A stream that receives nothing for longer than the client's
timeoutthrowsApiTimeoutException. The server sends a heartbeat line about every 15 seconds, so atimeoutunder 15 seconds breaks healthy streams. - Reconnecting: The API has no resume cursor, so events sent between connections are missed.
Call stream() again to reconnect:
while (true) {
try (EventStream<ProxyEvent> events = client.proxies().stream()) {
for (ProxyEvent event : events) {
System.out.println(event.ip() + " " + event.provider() + " " + event.type());
}
}
}
The loop reconnects immediately after a clean close. Opening a connection already uses the SDK's bounded retries, including Retry-After handling. Any error that remains after those retries, or occurs while reading the stream, stops this consumer. A 429 without Retry-After can indicate an exhausted monthly request budget or too many open streams; resolve that condition before reconnecting.
Every feed streams the same way. Only the event type changes:
Streams
try (EventStream<ProxyEvent> events = client.proxies().stream()) {
for (ProxyEvent event : events) {
System.out.println(event.ip() + " " + event.provider() + " " + event.type() + " " + event.asn());
}
}
Exports
Each feed has hourly Parquet exports that roll up into daily exports.
- Hourly exports: An hourly export is available until its day is rolled up into the daily export, shortly after 00:30 UTC the next day. After that, requests for the hour return a 400
ApiException. - Daily exports: While a daily export is being rolled up, requests for it return a 404
ApiExceptionwithretryAfter()set.
Export IDs
An export ID is one of:
ExportId.LATEST- a UTC day such as
"2026-05-03" - an hour of a UTC day such as
"2026-05-03/7", where a zero-padded hour is also accepted
Export.id() values can be passed as they are. Any other string, including a day that does not exist, throws IllegalArgumentException before a request is sent.
- Name
ExportId.LATEST- Type
- String
- Description
"latest", the newest export: the latest hourly export during the day, then the daily rollup.
ExportId.daily returns the ID of the daily export of a UTC day. ExportId.hourly returns the ID of the hourly export of an hour of a UTC day.
public static String daily(LocalDate date)
public static String hourly(LocalDate date, int hour)
ZonedDateTime lastHour = ZonedDateTime.now(ZoneOffset.UTC).minusHours(1);
String hourly = ExportId.hourly(lastHour.toLocalDate(), lastHour.getHour());
String daily = ExportId.daily(LocalDate.now(ZoneOffset.UTC).minusDays(1));
System.out.println(hourly + " " + daily);
- Input:
ExportId.hourlythrowsIllegalArgumentExceptionif the hour is not between 0 and 23.
listExports
Returns one page of the feed's exports, newest first.
public ExportPage listExports()
public ExportPage listExports(int limit, @Nullable String cursor)
ExportPage page = client.proxies().listExports(24, null);
for (Export export : page.exports()) {
System.out.println(export.id() + " " + export.kind() + " " + export.rowCount());
}
System.out.println("next cursor: " + page.nextCursor());
- Limit:
limitis the page size; the API allows up to 500 and clamps larger values. Without arguments, it returns the first page with the API's default page size of 100. - Input: A limit below 1 throws
IllegalArgumentException. - Paging:
cursorcontinues from a previous page'snextCursor(), or isnullfor the first page. UseallExportsto iterate over every page.
allExports
Returns every export from cursor on, newest first, fetching pages as iteration reaches them.
public Iterable<Export> allExports()
public Iterable<Export> allExports(int limit, @Nullable String cursor)
for (Export export : client.proxies().allExports()) {
System.out.println(export.id() + " " + export.kind() + " " + export.sizeBytes());
}
- Input:
limitandcursorwork as inlistExports. - Paging: Each call to
iterator()starts again fromcursor, or from the first page when it isnull, and stopping early fetches nothing more. - Errors: A failed page throws from
hasNext()and can be retried by calling it again. A cursor the API already returned throwsSynthientExceptionrather than looping.
exportMeta
Returns the checksum, size, row count, and Parquet columns of an export.
public ExportMeta exportMeta(String id)
ExportMeta meta = client.proxies().exportMeta(ExportId.LATEST);
System.out.println(meta.id() + " " + meta.rows() + " " + meta.size() + " " + meta.checksum());
for (ParquetColumn column : meta.schema().fields()) {
System.out.println(column.name() + " " + column.type());
}
downloadUrl
Returns the presigned URL of an export's Parquet file.
public URI downloadUrl(String id)
URI url = client.proxies().downloadUrl(ExportId.LATEST);
System.out.println(url.getHost());
- Lifetime: The URL is valid for 24 hours.
- Security: The URL grants access to the file without an API key, so treat it as a secret.
- Use: It is useful for tools that read Parquet over HTTPS directly.
- Errors: If the API does not redirect to a usable location, or redirects from
httpstohttp, it throwsSynthientException.
download
Returns the contents of an export's Parquet file.
public InputStream download(String id)
- Cleanup: The caller must close the returned stream.
- Security: The API key is sent only to the Synthient API, never to the storage host that serves the file, and errors never include the presigned URL.
- Errors: An error status from the storage host throws an
ApiExceptionwithout the response body. The download from the storage host is not retried and is not reported to the response hook. - Timeout: The client's
timeoutapplies while reading. If the transfer stalls or drops, reading throws anIOException, such asjava.net.SocketTimeoutException, rather than an SDK exception.
The SDK does not verify the file. Compare its SHA-256 with Export.checksum() or ExportMeta.checksum(). Use the same dated ID for the metadata and the download, because ExportId.LATEST can move to a newer export in between:
static void saveExport(Feed<?> feed, String id, Path path) throws IOException, NoSuchAlgorithmException {
ExportMeta meta = feed.exportMeta(id);
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
try (InputStream body = new DigestInputStream(feed.download(id), sha256);
OutputStream out = Files.newOutputStream(path)) {
body.transferTo(out);
}
if (!HexFormat.of().formatHex(sha256.digest()).equals(meta.checksum())) {
throw new IOException("checksum mismatch");
}
}
LocalDate yesterday = LocalDate.now(ZoneOffset.UTC).minusDays(1);
saveExport(client.proxies(), ExportId.daily(yesterday), Path.of("proxies.parquet"));