Skip to main content
A query-based source: the extract is a parameterised SELECT with a watermark predicate, read through the Storage Read API and run on a schedule.

What you need

  • roles/bigquery.dataViewer on the dataset and roles/bigquery.jobUser on the project
  • An Octave workspace API key, from Settings → Integrations
  • A Google Cloud project to deploy into

Where this runs

A Cloud Run job on Cloud Scheduler is the natural fit. Jobs run for up to 24 hours, so the backfill and the hourly incremental run can be the same container with different arguments.

The prompt

Copy this into Claude Code, Cursor or any coding agent. It carries the whole flow — reading your warehouse, mapping the columns, posting to Octave, and deploying the result. Fill in section 1 with your real schema and a few sample rows (bq show --schema --format=prettyjson analytics:gtm.opportunities plus a TABLESAMPLE of a few rows). The more of your actual data it can see, the less it has to guess.
The instruction to show the mapping as a table and wait for confirmation is the part worth keeping. Without it an agent guesses at your column names and buries the guess inside a handler, where a wrong eventType looks exactly like a right one until the data is in.

Hints worth adding

These are the things that go wrong with BigQuery specifically. Paste whichever apply into section 1 of the prompt — an agent cannot infer them from a schema. DATETIME and TIMESTAMP are not the same thing. One has no timezone, the other is an absolute instant. Mixing them shifts every event by your offset and nothing errors. Say which type each column is. Partition pruning only happens on the partitioning column. If modified_at is not it, an incremental query scans the whole table every hour. Mention the real partition key so the AI adds a predicate on it. NUMERIC loses precision through JSON. Ask for a cast to string in the query and amount sent as a string.