# Datasets overview A dataset is a project-owned table of observations — subdomains, live hosts, findings, or anything else your workflows collect. Observations are append-only: each push records a new snapshot with an ingest timestamp, and the service derives the current state from the history. Open a project's **Datasets** tab to browse the Query, Table, Views, and Notifications surfaces. This page explains the model; the rest of the section covers each surface in depth. ## The model
Concept Meaning
Observation One row pushed with step.update_dataset or IngestDatasetRows .
Ingest timestamp __ingested_at , recorded for every observation.
Identity The column(s) that make two observations the same logical record.
Current state The latest observation per identity, with stale rows removed.
History Every observation ever pushed, including superseded ones.
View A saved read-only SQL query over project datasets and other views.
### Current state and history By default, queries run over the **current state**: for each identity, only the latest observation (by `__ingested_at`) is returned, and rows marked stale are excluded. A dataset without identity columns has no reconciliation key, so every pushed row is its own record. Set `includeHistory = true` (UI: **Include history**; CLI: `--include-history`) to run over the raw append-only stream instead. History keeps every observation, including duplicate identities and older versions. ### Reserved columns Every dataset relation exposes two internal columns in addition to your schema:
Column Type Meaning
__ingested_at timestamp When the observation was pushed.
__deleted boolean Whether the record is marked stale. Current-state queries exclude true rows.
Both are reserved names and cannot be added to a dataset schema. ## Removing rows `DeleteDatasetRows` removes records selected by a structured filter. `mode` chooses what happens to the stored observations:
Mode Effect
DATASET_DELETE_MODE_SOFT Appends tombstones for the matching identities. Current-state queries hide the records; history keeps the tombstones and the observations they supersede. Requires identity columns.
DATASET_DELETE_MODE_HARD Rewrites stored files to remove every observation belonging to the matching records. History no longer contains them.
`ClearDataset` removes every record and stored file in the table. Deleting a dataset, clearing it, or hard-deleting rows releases the parquet storage from the account's storage usage; soft deletes and new observations record the bytes they add. ## Pushing observations Workflows push transform output into an existing dataset. The dataset service owns identity matching, reconciliation, and storage; the workflow supplies only the rows and the mode. ```bhw step.update_dataset( table = "subdomains", source = path.file("subs-clean.txt").lines(), pipeline = transform.trim().lower().unique(), mapping = { "host": row.value, "source": "subfinder", }, mode = "upsert", ) ``` - `mode = "upsert"` updates or creates records matching known identities; identities absent from the push are untouched. - `mode = "sync"` declares the push authoritative: identities absent from the push are marked stale. See [Pushing dataset rows](/datasets/workflows) for mapping, transform sources, and a complete asset-tracking workflow. ## Query surfaces
Surface Runs over Where
Single-dataset SQL One dataset, current state or history Datasets → Query → Advanced SQL
Structured filters The result of the single-dataset query Datasets → Query → Filters
Saved views Project datasets and other views, current state only Datasets → Views
Project-wide SQL Every dataset in a project, current state only API QueryDatasets
Describe Column null counts, distinct counts, top values CLI bh dataset describe or API
Export A full single-dataset query result Datasets query download or API
The only relations a query may reference are the ones registered for its scope: `dataset` for a single-dataset query, or the project's dataset names for a project-wide query. File scans, catalog tables, table functions, and other datasets are rejected. See [SQL reference](/datasets/sql) for the full policy. ## Dataset notifications Each dataset can route row changes to email, Discord, or Slack channels from the dataset's **Notifications** tab. Create the integration and notification channel first, then assign it to the dataset and choose the routes: rows added, removed, or changed. See [Notifications overview](/notifications/overview) for channel setup. ## Limits
Limit Value
Datasets per project 100
Views per project 100
Single-dataset and project-wide page size Default 100, maximum 1,000
Saved-view page size Default 100, maximum 10,000
Row materialization guard (engine) 100,000
Query memory 2 GB by default
Concurrent interactive queries per user 2
Interactive queries per user per minute 60
Export download URL lifetime 15 minutes
Workflow runners are exempt from the interactive fairness limits; they are bounded by runner capacity instead. Operators can tune the defaults with `BOUNTYHUB_HUB_DATASET_QUERY_MAX_ROWS`, `BOUNTYHUB_HUB_DATASET_QUERY_MEMORY_LIMIT`, `BOUNTYHUB_HUB_DATASET_QUERY_MAX_CONCURRENT_PER_USER`, and `BOUNTYHUB_HUB_DATASET_QUERY_PER_MINUTE`. ## Next steps - [Schemas and identity](/datasets/schema): design a dataset before you push. - [Pushing dataset rows](/datasets/workflows): push from a workflow. - [SQL reference](/datasets/sql): query the data with DuckDB. - [Structured filters](/datasets/filters): filter without writing SQL. - [Saved views](/datasets/views): keep reusable queries in the project. - [API and CLI](/datasets/api-cli): automate datasets outside the Hub UI.