How to connect BigQuery with a read-only service account
Step by step: create a BigQuery service account with the two roles it actually needs, scope it to the datasets you want exposed, download the key, and connect it to Contextflo.
Connecting BigQuery takes about ten minutes. Most of that is clicking through the Google Cloud Console, and the only decision that really matters is how narrowly you scope the service account.
This guide covers the whole thing: creating the account, giving it the two roles it needs and nothing else, downloading the key, and connecting it. It also covers the part people skip, which is deciding what the account should be able to see before you create it rather than after.
Before you start
You need:
- Admin access to a Google Cloud project
- The BigQuery API enabled on that project
- Permission to create service accounts and assign IAM roles
If you do not have IAM permissions yourself, this is a ten-minute ask for whoever does. Send them this page.
Step 1: Create the service account
A service account represents an application rather than a person. Contextflo authenticates as this account to read your data, which means everything it can ever see is decided here.
Open IAM & Admin. In the Cloud Console, navigate to IAM & Admin → Service Accounts, or search "Service Accounts" in the top bar.
Create the account. Click Create service account:
- Name:
contextflo-bigquery - ID: auto-generated from the name
- Description: "Read-only access for Contextflo"
Name it for what it is. Two years from now somebody will audit your service accounts, and contextflo-bigquery answers their question without them having to ask you.
Grant the roles. Click Continue and add exactly two:
BigQuery Data Viewer— read tables and datasetsBigQuery Job User— run queries
Both are read-only. There is no write, no delete, no schema change available through either of them, so a compromised key cannot damage your warehouse. It can read, which is the risk you are actually managing, and that is what Step 2 below is about.

Click Continue, then Done. Skip the optional "Grant users access" step.
Step 2: Decide what it can see
This is the step worth slowing down for. Granting BigQuery Data Viewer at the project level means every dataset in that project, including ones created next quarter by somebody who has never heard of this connection.
You have three options, in increasing order of control:
Project-level Data Viewer. Simplest. Fine for a project that only holds analytics data you are comfortable exposing. Not fine for a project that also holds raw exports, HR data, or anything under a data processing agreement.
Dataset-level Data Viewer. Skip the project-level grant, and instead go to each dataset you want exposed → Sharing → Permissions and add the service account as Data Viewer there. The account still needs BigQuery Job User at the project level to run queries at all, but its read access is now an explicit allowlist. This is the option I would default to.
A views-only dataset. For anything with PII or column-level sensitivity, expose views rather than tables — the account queries a dataset that contains nothing but views you have shaped, and genuinely cannot reach the underlying tables. That is a longer setup, covered in restricting BigQuery access with authorized views.
The right answer depends on what is in the project, and it takes five minutes to check. Reviewing the dataset list once now is much easier than working out, later, whether an assistant could have read something it should not have.
Step 3: Download the JSON key
In the Service Accounts list, open the contextflo-bigquery account. Go to the Keys tab, click Add key → Create new key, choose JSON, then Create. The file downloads automatically.
The file looks like this, with the sensitive parts redacted:
{
"type": "service_account",
"project_id": "your-project-id",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]"
}
That private_key is a live credential with no expiry. It is valid until somebody explicitly deletes the key in the console. Do not commit it, do not paste it into Slack, and delete the download from your machine once it is uploaded.
Step 4: Connect it
In Contextflo, go to Data Sources and click Add data source → BigQuery. Fill in:
- Project ID — the
project_idfrom the JSON key - Location — where your datasets live, e.g.
US,EU,us-central1 - Service account JSON — upload the file or paste its contents
Click Test connection, then Connect. Contextflo will discover the datasets and tables this account can read, which should be exactly the list you decided on in Step 2. If it is longer than you expected, that is worth investigating before you go further, and it usually means a project-level grant is in place.
When the connection fails
Four failures cover almost everything:
Permission denied. The account is missing a role. In IAM & Admin, confirm both BigQuery Data Viewer and BigQuery Job User are assigned. If you scoped Data Viewer at the dataset level, check the dataset's own Sharing → Permissions, not the project IAM page — they are different screens and the project page will not show it.
Invalid JSON. The key file is incomplete or malformed, usually from a partial copy-paste. Re-download it and upload the file rather than pasting.
Project not found. Either the Project ID is wrong, or the BigQuery API is not enabled. Enable it under APIs & Services → Library.
Location mismatch. The location must match where your datasets actually live. A US-region connection cannot see EU datasets. Check the dataset's details page for its region.
Keeping it healthy
A few things that take minutes now and save an incident later:
- Rotate the key. Every 90 days is a reasonable cadence. Create the new key, update the connection, then delete the old one — in that order, so nothing breaks in between.
- Keep it read-only. If someone later asks you to add a write role to this account "just for one thing", that is a new account, not an extra role on this one.
- Watch the audit logs. Cloud Audit Logs record every query this account runs. If you ever need to answer "what did this thing actually read", it is already recorded.
- Delete unused keys. A service account can hold several keys and they all keep working. The Keys tab is the only place that tells you how many are live.
What's next
With BigQuery connected, the next step is context: telling the model what your tables mean, so questions get answered against your definitions rather than the model's guesses about your column names.
- Define metrics so revenue means the same thing every time
- Metric trees for connecting those metrics into a structure
- How to use Claude with BigQuery to start asking questions
Questions on any of this: [email protected], or see the connection docs.