Skip to main content

Helium Mobile Network Data

Helium Mobile Network data is published as Apache Iceberg tables that anyone can query with SQL. The tables are stored as Parquet files in Cloudflare R2 and served through a public Iceberg REST catalog. Point Trino, PyIceberg, Spark, or any other Iceberg-aware engine at the catalog to query data transfer sessions, Hotspot heartbeats, speedtests, bans, and rewards.

The catalog needs no credentials, and R2 does not charge egress, so reads are not billed. The only cost is the compute for your own query engine.

Migration in progress

Helium is moving Mobile Network data from gzipped protobuf files in S3 (Oracle Data) to these Iceberg tables. The switch-over is due to complete on November 1, 2026. Until then, some tables are written to both places while the new pipeline is validated against the old one.

These tables cover the Mobile Network only. IoT Network data is not included.

For background on why the data moved and how the pipeline works, read Rebuilding how Helium stores and queries network data on the Helium blog.

How It Works​

The stack has three parts:

  • Apache Iceberg is the table format. Data lands as immutable Parquet files in object storage, alongside metadata that describes the columns, the partitioning, and which files belong to each committed snapshot.
  • An Iceberg REST catalog tracks which tables exist and where each table's current metadata lives. Writers and readers both go through it, so they always agree on what a table contains.
  • Trino is the SQL query engine Helium runs. Any engine with Iceberg support can read the same tables.

The Mobile Oracles write to these tables directly. For tables that have finished moving, the Oracles also read their reward inputs from them, so the data you query is the data rewards are based on. Iceberg commits are atomic, so a reader never sees a partially written batch.

Available Tables​

Tables are grouped into namespaces, each matching a stage of the pipeline that produces it. The SQL examples on this page use Trino syntax, with the catalog named mobile as set up in Connecting to the Catalog.

TableDescriptionPartitioned ByHistory From
data_transfer.sessionsAn offload session that passed verification, with carrier, byte counts, and payerday(report_received_timestamp)Oct 2024
data_transfer.burned_sessionsA Hotspot's traffic billed in one DC burn, with byte and DC counts and its HIP-150 multiplier (null before HIP-150)day(burn_timestamp)Apr 2023
data_transfer.multiplier_ticket_historyA HIP-150 multiplier ticket, accepted or refused (the table is append-only)bucket(hotspot_pubkey, 4)No rows yet
hotspots.heartbeatsA validated heartbeat, with position and location trust multiplierday(received_timestamp)Oct 2023
hotspots.speedtestsA Hotspot's validated speedtest resultday(received_timestamp)Sep 2023
hotspots.speedtest_avgsA Hotspot's rolling speedtest averageday(timestamp)Jan 2023
hotspots.bansA ban or unban applied to a Hotspotday(received_timestamp)Apr 2025
rewards.data_transferA Hotspot's data transfer payout for a period, with rewardable bytes and the price usedday(start_period)May 2023
rewards.service_providerA service provider's payout for a periodday(start_period)Jan 2024
rewards.unallocatedA period's emissions not allocated to any entityday(start_period)Jan 2024

History in the data_transfer and hotspots tables from before the move to Iceberg was loaded from the S3 archive and includes only records that passed validation. In backfilled heartbeats, heartbeat_timestamp equals received_timestamp and asserted_location is null. rewards.service_provider has no rows after August 30, 2026, because HIP-150 contributes Service Provider rewards to deployers.

Other Tables​

The catalog also holds these tables:

TableDescriptionPartitioned ByHistory From
data_transfer.invalid_sessionsA session that failed verification, with a reasonday(report_received_timestamp)Jun 2026
hotspots.invalid_heartbeats, invalid_speedtests, invalid_speedtest_avgsA record that failed validation, with a reasonSame as the valid tableJun 2026
hotspots.usage_statsA carrier's daily usage report for a Hotspot: users and bytes, in total and per carrierday(epoch_start_timestamp)Dec 2025
hotspots.enabled_carriers_historyA report of which carriers a Hotspot has enabled for offload and samplingday(received_timestamp)Oct 2025
rewards.proof_of_coverageA Hotspot's Proof-of-Coverage reward for a period, through July 5, 2026day(start_period)Apr 2023
rewards.covered_hexesOne hex a Hotspot covered in a Proof-of-Coverage reward period, through July 5, 2026day(start_period)Jul 2024

Each invalid_* table has the same columns as its valid counterpart, plus reason. hotspots.invalid_bans also exists but has no rows yet.

To list the namespaces, the tables in each, and a table's current schema, ask the catalog:

SHOW SCHEMAS IN mobile;
SHOW TABLES IN mobile.hotspots;
DESCRIBE mobile.hotspots.heartbeats;
SELECT * FROM mobile.information_schema.columns WHERE table_schema = 'hotspots';

The listings also include items this page does not cover, such as one-off backup tables and the poc schema, which holds compatibility views.

Filter on the Partition Column​

Always filter on a table's partition column. That predicate lets Iceberg skip files that cannot match. Without it, a query reads the whole table, however narrow the result looks.

SELECT hotspot_pubkey, count(*)
FROM mobile.hotspots.heartbeats
WHERE received_timestamp >= TIMESTAMP '2026-09-21 00:00:00 UTC'
AND received_timestamp < TIMESTAMP '2026-09-22 00:00:00 UTC'
GROUP BY hotspot_pubkey

The filter must be on the partition column itself. Filtering on a different timestamp in the same row prunes nothing, even when the two values move together.

data_transfer.multiplier_ticket_history is the exception. It is bucketed by hotspot_pubkey, so a filter on the pubkey prunes it and a date range does not.

Keep timestamp literals to six or fewer fractional digits. Trino gives a literal with more digits a higher precision, such as timestamp(9), and casts the column up to match, which turns pruning off and scans every partition.

Reading the Data​

  • Hotspot key columns have different names. The Hotspot's key is data_transfer_event_pub_key in data_transfer.sessions and data_transfer.invalid_sessions, pub_key in data_transfer.burned_sessions, pubkey in hotspots.bans and hotspots.invalid_bans, hotspot_key in rewards.data_transfer, rewards.proof_of_coverage, and rewards.covered_hexes, and hotspot_pubkey in the other hotspots tables and data_transfer.multiplier_ticket_history.
  • Reward amounts are integers in the reward token's smallest unit. Reward periods from January 29, 2025 onward are paid in HNT: amounts are in bones (1 HNT = 100,000,000 bones), and price in rewards.data_transfer is the HNT price in USD multiplied by 10^8. Earlier periods were paid in MOBILE, which has 6 decimals, and price is the MOBILE price multiplied by 10^6.
  • Speedtest speeds are in bytes per second. That includes columns whose names end in _bps. Latency is in milliseconds.
  • Carriers appear as numbered IDs. Carrier columns hold values such as carrier_1, not carrier names.
  • hotspots.usage_stats can repeat a report. The same Hotspot and day can appear more than once. Keep the latest row per hotspot_pubkey and epoch_start_timestamp, by received_timestamp, before summing.

Connecting to the Catalog​

The catalog is a standard Iceberg REST endpoint. Every client needs the same values:

SettingValue
Catalog URIhttps://catalog.mobile.helium.io/api/catalog
Warehousemobile
Storage endpointhttps://helium.io
Storage regionus-east-1
Path-stylefalse
Access keyanonymous
Secret keyanonymous

These are the live values, not placeholders. The catalog is unauthenticated, and the storage credentials are the literal string anonymous, so there is nothing to request or keep secret.

Add a catalog file to your Trino server. See the Trino Iceberg connector docs for every available property, and Deploying Trino to stand up a server.

etc/catalog/mobile.properties
connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=https://catalog.mobile.helium.io/api/catalog
iceberg.rest-catalog.security=NONE
iceberg.rest-catalog.warehouse=mobile
iceberg.rest-catalog.vended-credentials-enabled=false
iceberg.security=READ_ONLY
fs.native-s3.enabled=true
s3.region=us-east-1
s3.endpoint=https://helium.io
s3.path-style-access=false
s3.aws-access-key=anonymous
s3.aws-secret-key=anonymous

Tables are then addressed with the catalog name in front, for example mobile.hotspots.heartbeats. A second .properties file adds a second catalog, and Trino can join across them, so you can query this data alongside your own in a single statement.

Things to Know​

  • warehouse must be mobile. It is the warehouse name the REST catalog knows, not a bucket name. With any other value, the catalog connects but lists no tables.
  • Vended credentials are off. The catalog does not return per-table storage credentials, so your client must supply the storage settings above for every table it reads.
  • The catalog is rate-limited. A client that sends too many requests in a short time has some of them rejected.