Skip to main content
Technical Preview

Overview

A client-mode connector for the BigQuery API v2 that streams MQTT telemetry into BigQuery tables and runs SQL from MQTT: Default BASE_ADDRESS is https://bigquery.googleapis.com. Replace YOUR_PROJECT_ID in the endpoints with your GCP project id. Topics use data/google-bigquery/<object>/<verb>.
Storage Write API is not included. The BigQuery Storage Write API (and Storage Read API) is gRPC/protobuf only, so a REST_API connector can’t call it. This template uses the legacy tabledata.insertAll streaming endpoint, which is plain HTTPS + JSON. Load jobs with media upload (/upload/bigquery/v2/…) and destructive deletes are also left out.

Auth

BigQuery needs an OAuth 2.0 access token in Authorization: Bearer …. The template reads it from the MQTT payload field access_token; keep the token in a Hub secret such as GET SECRET "GOOGLE_BIGQUERY_ACCESS_TOKEN" and never commit it.
  • Minting the token is out of band. Exchange a service-account key (signed JWT) for an access token with gcloud auth print-access-token, a Google auth SDK, or your own token service, then publish or refresh it. Access tokens expire after about 1 hour.
  • Scopes: https://www.googleapis.com/auth/bigquery (full), https://www.googleapis.com/auth/bigquery.insertdata (streaming insert only), or https://www.googleapis.com/auth/cloud-platform.
  • IAM: the service account needs BigQuery Data Editor on the dataset and BigQuery Job User on the project to run queries.
A 401 UNAUTHENTICATED response with Request had invalid authentication credentials means the token is missing, malformed, or expired.

Basic Syntax

Request bodies come from {value.json.body}, not {payload}. The MQTT payload carries control fields (access_token, datasetId, tableId, jobId, location, maxResults) next to a body object, and only body is sent to Google. Google APIs reject unknown top-level fields with 400 Invalid JSON payload received.

Example MQTT payloads

Create a dataset (topic data/google-bigquery/dataset/create):
Create a telemetry table (topic data/google-bigquery/table/create):
Stream rows (topic data/google-bigquery/rows/insert). insertId gives best-effort de-duplication on retries:
A clean insert returns {"kind":"bigquery#tableDataInsertAllResponse"}. Rejected rows return HTTP 200 with insertErrors (for example no such field: pressure.), so check the response body, not just the status. Run SQL (topic data/google-bigquery/query/run):
Fetch results or job status (topics data/google-bigquery/query/results, data/google-bigquery/job/get):
Always send location and maxResults for the GET events, since the template puts them in the query string. Use the dataset’s location (US, EU, europe-west1, …); jobs outside the US and EU multi-regions are not found without it.

Quotas and limits

  • insertAll: up to 10 MB per request; Google recommends about 500 rows per request. Streamed rows are queryable within seconds, but they can’t be changed with DML for a short window after insert.
  • jobs.query waits up to timeoutMs (default 10 s). If jobComplete is false, poll GetQueryResults with jobReference.jobId.
  • On-demand queries are billed by bytes scanned. Use partitioned tables and WHERE filters on high-rate telemetry. See BigQuery quotas.
Last modified on October 26, 2026