# Datasets API and CLI Datasets are exposed through two ConnectRPC services: - `bountyhub.dataset.v1.DatasetService` — datasets, ingest, SQL, describe, export. - `bountyhub.dataset.v1.DatasetViewService` — saved views. The generated TypeScript SDK exposes `datasetClient` and `datasetViewClient`; the `bh` CLI wraps the most common `DatasetService` calls. In Connect JSON, `int64` and `uint64` fields (for example `rowCount`) are encoded as strings. ## Authentication and scopes
Access Scope
Get, list, query, project-wide query, describe, export, execute view DatasetRo
Create, update, delete, ingest, delete rows, clear, create/update/delete view DatasetRw
Create a Personal Access Token with the **Datasets** scope (and **Projects** read, which it requires). See [Personal Access Token scopes](/developer/scopes). ## DatasetService
RPC Purpose Pagination
CreateDataset Create a table and schema —
GetDataset Read one dataset —
ListDatasets List project datasets Default 50, max 500
UpdateDataset Change name or description —
DeleteDataset Delete the dataset and its files —
IngestDatasetRows Append observations —
DeleteDatasetRows Soft- or hard-delete records selected by a filter —
ClearDataset Remove every record and stored file —
QueryDataset One read-only SELECT over dataset Default 100, max 1,000
QueryDatasets One SELECT over every dataset in a project Default 100, max 1,000
DescribeDataset Null/distinct counts and top values —
ExportDataset Stream a full query result to a file —
### CreateDataset ```json { "projectId": "", "name": "subdomains", "description": "Resolved subdomains per program", "schema": { "columns": [ { "name": "host", "kind": "COLUMN_KIND_TEXT", "nullable": false, "identity": true }, { "name": "program", "kind": "COLUMN_KIND_TEXT", "nullable": false, "identity": true }, { "name": "resolved", "kind": "COLUMN_KIND_BOOLEAN", "nullable": true } ] } } ``` Returns `{ "id": "" }`. Names must be unique per project, sharing one case-insensitive namespace with saved views; up to 100 datasets are allowed. See [Schemas and identity](/datasets/schema). ### IngestDatasetRows ```json { "datasetId": "", "mode": "DATASET_INGEST_MODE_UPSERT", "rows": [ { "fields": { "host": { "textValue": "api.example.com" }, "program": { "textValue": "example" }, "resolved": { "booleanValue": true } } } ] } ``` - `fields` is keyed by column name; the service maps values by schema order. - Missing fields become `NULL`; a `NULL` in a non-nullable column fails. - Values must match the column kind (see [Structured filters](/datasets/filters#value-types) for the value shape). - `mode` is `DATASET_INGEST_MODE_UPSERT` (default) or `DATASET_INGEST_MODE_SYNC`. - Returns `{ "rowCount": "" }`. ### DeleteDatasetRows ```json { "datasetId": "", "mode": "DATASET_DELETE_MODE_SOFT", "filter": { "condition": { "column": "host", "op": "FILTER_OP_EQ", "values": [{ "textValue": "api.example.com" }] } } } ``` `filter` is required and selects records from the current logical state. `DATASET_DELETE_MODE_SOFT` appends tombstones and requires identity columns; `DATASET_DELETE_MODE_HARD` removes the matching observations from stored files. Returns `{ "deletedRowCount": "", "storageDeltaBytes": "" }`. ### ClearDataset ```json { "datasetId": "" } ``` Removes every record and stored file. Returns `{ "deletedRowCount": "", "storageDeltaBytes": "" }`. ### QueryDataset ```json { "datasetId": "", "sql": "SELECT host, ip FROM dataset WHERE resolved", "pageSize": 100, "pageToken": "", "includeHistory": false, "ingestedAtFrom": "2026-01-01T00:00:00Z", "filter": { "condition": { "column": "ip", "op": "FILTER_OP_IS_NOT_NULL", "values": [] } } } ```
Field Meaning
sql One read-only SELECT; a bare SELECT * becomes SELECT * FROM dataset .
pageSize Default 100, max 1,000.
pageToken Cursor from nextPageToken .
includeHistory Query raw observations instead of current state.
ingestedAtFrom / ingestedAtTo Bound __ingested_at .
filter Typed filter tree; see Structured filters .
The response contains `schema.columns`, `rows` (`values` in schema order), and `nextPageToken`. ### QueryDatasets ```json { "projectId": "", "sql": "SELECT s.host, w.status_code FROM subdomains AS s JOIN \"web-services\" AS w ON s.host = w.host WHERE s.resolved", "pageSize": 100 } ``` Returns `{ "result": { "columns", "rows", "nextPageToken" } }`. Filter with the SQL `WHERE` clause; the per-relation `filters` field is reserved and not supported by the current execution path. This path has no history mode. Project-wide SQL is API-only; the Hub UI's SQL box is scoped to one dataset. ### DescribeDataset ```json { "datasetId": "" } ``` Returns one entry per schema column with:
Field Meaning
nullCount Rows where the column is NULL .
distinctCount Distinct non-null values.
topValues Up to 10 most frequent non-null values, most frequent first.
`bh dataset describe` prints the first three top values. ### ExportDataset ```json { "datasetId": "", "format": "DATASET_EXPORT_FORMAT_CSV", "sql": "SELECT * FROM dataset", "includeHistory": false } ```
Format Enum Content type
CSV DATASET_EXPORT_FORMAT_CSV text/csv
JSON Lines DATASET_EXPORT_FORMAT_JSONL application/x-ndjson
JSON array DATASET_EXPORT_FORMAT_JSON application/json
- `sql` is required; use `SELECT * FROM dataset` for a full export. - Filters and time bounds use the same outer semantics as `QueryDataset`. - Exports have no page size or row cap; rows stream to the file. - The response returns `url` (presigned, valid for **15 minutes**), `fileName`, `contentType`, `rowCount`, and `sizeBytes`. ## DatasetViewService
RPC Scope Notes
CreateDatasetView DatasetRw Validates SQL, references, and cycles.
GetDatasetView DatasetRo Returns valid and invalidReason .
ListDatasetViews DatasetRo Default 50, max 500.
UpdateDatasetView DatasetRw Changes name, description, and SQL; resets validity.
DeleteDatasetView DatasetRw Invalidates dependent views.
ExecuteDatasetView DatasetRo Default 100, max 10,000.
`ExecuteDatasetView` returns `{ "result": { "columns", "rows", "nextPageToken" } }`. Views run over the current state and have no filter, history, or time-range fields; see [Saved views](/datasets/views). ## bh CLI The CLI wraps `DatasetService` for the common read and manage paths: ```bash # List datasets in a project bh dataset list --project-id "$PROJECT_ID" --all # Inspect one dataset and its columns bh dataset get --dataset-id "$DATASET_ID" bh dataset describe --dataset-id "$DATASET_ID" # Create with a schema file (proto JSON) bh dataset create \ --project-id "$PROJECT_ID" \ subdomains \ --description "Resolved subdomains per program" \ --schema-json schema.json # Rename or change the description bh dataset update \ --dataset-id "$DATASET_ID" \ --name subdomains-2026 \ --description "Subdomains discovered in 2026" # Query current state or history bh dataset query \ --dataset-id "$DATASET_ID" \ "SELECT host, ip FROM dataset WHERE resolved" bh dataset query \ --dataset-id "$DATASET_ID" \ --include-history \ "SELECT host, \"__ingested_at\" FROM dataset" # Delete bh dataset delete --dataset-id "$DATASET_ID" ``` `bh dataset query` prints a `next page token` line when more rows are available and accepts `--page-size` (default 100). It has no page-token flag; use the API (`QueryDataset`) to continue past the first page. There are no CLI commands for project-wide SQL, saved views, exports, or row ingestion — use the API for those. ## Errors
Connect code Cause
UNAUTHENTICATED Missing or invalid token.
PERMISSION_DENIED The PAT lacks DatasetRo / DatasetRw .
NOT_FOUND Unknown dataset or view, or one outside the caller's projects.
ALREADY_EXISTS Dataset name already used in the project.
INVALID_ARGUMENT Invalid schema, SQL, filter, id, or page token; a view cannot be evaluated.
RESOURCE_EXHAUSTED Too many concurrent queries or too many per minute.
## Related - [Schemas and identity](/datasets/schema) — column kinds and identity. - [SQL reference](/datasets/sql) — query language and limits. - [Structured filters](/datasets/filters) — filter AST and value types. - [Saved views](/datasets/views) — view semantics. - [Personal Access Token scopes](/developer/scopes) — scope names.