A governed analytics portal over Snowflake and BigQuery
A self-serve portal on the warehouse — with the two things it will not give your application: access control it can enforce without a warehouse admin, and a spend ceiling on every query your users type.
Warehouses are excellent at answering questions and terrible at deciding who may ask them — or how much an answer is allowed to cost. Supero binds warehouse tables as live-read schemas, applies your roles on top, and runs every query under explicit row, byte and time caps.
The BI request that never ends
Sales leadership wants a portal: pipeline by region, revenue by segment, a drill-down into the underlying facts, and the ability to ask a question nobody thought to build a chart for. The data is already in Snowflake and BigQuery. It is modelled, it is clean, it is correct.
And yet the portal takes a quarter, because the warehouse solves exactly one of the four problems. It has the answers. It has policies for who may see which rows and columns — administered by warehouse admins, in warehouse terms, per warehouse. What it does not have is any of that expressed in your application's roles, a spend ceiling on a query one of your users typed, or a surface a regional manager will actually open. An unbounded SELECT * typed at 4pm on a Friday can still bill four figures.
Two read paths, two different guarantees
This is the part most worth understanding before you design the portal, because the two paths differ in exactly one crucial way: whether the platform can safely inject a tenant filter.
access_mode: "warehouse" so your UI can detect a read-only schema without a second call and hide write controls entirely.run_query does not carry automatic tenant scoping. That is why it is refused for any connector whose tables use column-based tenant scoping, and why the warehouse role must be attested read-only. It is also not available on Microsoft Fabric.Four caps on every query
Warehouse compute is billed by the warehouse. Supero's caps are designed to fail a runaway query loudly rather than quietly scan a terabyte.
| Control | Default | What happens when it is hit |
|---|---|---|
| Row cap | 10,000 rows | A structured read stops and marks source_meta.scan as partial — it never fails. run_query cancels and fails with 413, because opaque SQL has no safe way to signal "more available". |
| Byte cap | 64 MiB buffered | The in-flight statement is cancelled; the call fails with 413 regardless of row count. |
| Query deadline | 90 seconds | Bounds the whole statement — submit, poll and drain — deliberately under the platform proxy timeout so Supero cancels first and returns a structured error. |
| Statement timeout | 60 seconds | Pushed to Snowflake and ClickHouse as a native query timeout that stops warehouse compute. BigQuery, Databricks and Redshift do not accept an equivalent — there the deadline bounds Supero's wait, and the query itself may run to completion. Lean on BigQuery's bytes-billed cap and your own LIMIT on those three. |
- BigQuery additionally enforces
maximumBytesBilled, on by default, so an unboundedSELECT *cannot produce a surprise bill even if you never configured a cap. Override per connector withmaximum_bytes_billed(0removes it explicitly). - ClickHouse: if your role already constrains
max_result_rowsormax_result_bytes, Supero drops the specific cap the error names and retries once, keeping the other as a backstop — your source-side constraint stays the effective limit instead of the read failing. - Redshift has no native per-query byte cap and takes no pushed-down statement timeout, so your own
LIMITand narrow filters are doing the work — the 90-second deadline bounds Supero's wait, not Redshift's compute. Filter on partitioned or clustered columns and scope date ranges narrowly.
How to build it on Supero
Mint a read-only warehouse role
Everything downstream depends on this, including whether the SQL console is available at all — run_query requires an attested read-only role. Grant SELECT on the specific tables the portal needs and nothing else.
⏱ ~15 minutes
Connect the warehouse
All six warehouses use plugin: "database" with a dialect-specific config. Snowflake authenticates with a key pair; BigQuery with a service account; Databricks with a token; Redshift with an IAM/Data-API credential. Secrets are held as secret_refs and resolved server-side at query time — a GET on a connector never echoes them.
// Snowflake
{ "plugin": "database", "config": {
"db_type": "snowflake", "account": "ORG-ACCOUNT", "warehouse": "COMPUTE_WH",
"database": "ANALYTICS", "schema": "PUBLIC", "role": "READER",
"username": "SVC_SUPERO", "private_key": "-----BEGIN PRIVATE KEY-----...",
"query_type": "table", "table_names": ["ORDER_FACTS", "CUSTOMERS"] } }
// BigQuery
{ "plugin": "database", "config": {
"db_type": "bigquery", "project_id": "my-gcp-project", "dataset": "analytics",
"location": "US", "service_account_json": { "...": "..." },
"query_type": "table", "table_names": ["orders"] } }⏱ ~20 minutes
Discover, then bind — and label your tables honestly
Every warehouse table must bind live_read; the platform validates this on connector create and on any update touching the table set, so a table cannot silently drift into an unsupported mode later. One consequence worth planning for: a stream you class as event is addressed by dimension and time range, not by uuid — get-by-uuid on it is refused. Drill-downs work on dimension tables; on a fact table, drill through to a dimension instead.
Set stream_class correctly while you are here: it distinguishes bounded dimension tables from time-series fact tables, and it changes how reads are planned. The stored binding key is field_map, always {schema_field: source_column}. Discovery only needs the columns you rename — every other discovered column gets an identity entry added automatically at confirm time, so the saved map covers the whole table rather than being sparse.
⏱ ~30 minutes
Layer the access model the warehouse does not have
This is the step that turns a warehouse into a portal. Roles, tenant isolation and field hiding apply to warehouse-backed schemas exactly as they do to native ones.
POLICIES = [
PolicyDef(role="tenant_admin", default_access="full", rules=[]),
# Managers: facts and customers, but never the margin columns.
PolicyDef(role="tenant_user", default_access="none", rules=[
PolicyRule(entity="order_facts", can_read=True,
hidden_fields=["unit_cost", "gross_margin"]),
PolicyRule(entity="customers", can_read=True,
hidden_fields=["lifetime_discount"]),
]),
PolicyDef(role="viewer", default_access="none", rules=[
PolicyRule(entity="order_facts", can_read=True,
hidden_fields=["unit_cost", "gross_margin"]),
]),
]Then test it as a user, not as an admin: sign in as viewer, try to sort by gross_margin, and confirm the refusal happens before the warehouse is ever billed for the query.
⏱ ~1 hour
Build the portal and switch on the agent
Generate the dashboards and detail views from the bound schemas. The in-app AI agent needs no wiring: it derives search_/get_ tools per bound entity plus shared analytics tools — db_stats for sums and averages, db_count_by for grouped counts, db_distinct for the value list behind a filter, db_aggregate for multi-stage grouping — and every one goes through the same access engine as the UI.
⏱ ~1–2 hours
Open the SQL console to the few who need it
Expose run_query behind an admin-only route. Always pass params rather than interpolating values, always set row_limit, and surface the rejection code verbatim — not_a_select, multiple_statements, forbidden_keyword — so an analyst can fix their own query.
curl -X POST https://api.supero.dev/api/v1/connectors/CONNECTOR_ID/run_query \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sql": "SELECT region, COUNT(*) AS orders, SUM(amount) AS total FROM order_facts WHERE order_date >= ? GROUP BY region ORDER BY total DESC",
"params": ["2026-01-01"],
"row_limit": 200
}'⏱ ~30 minutes
What a warehouse connector will not do
- No sync mode — there is no whole-warehouse copy into Supero.
- No
live_readwrite, and no reverse-ETL back into the warehouse. - Writes fail (403/405/409/400 depending on the path). If you need write access to the same system, point a regular database connector at it instead.
- Latency is your warehouse's latency, and repeated reads mean repeated warehouse compute. There is no staleness window — that is the trade.
run_queryreturns raw columns with no identity envelope, so it does not feed drill-downs, references or the agent. Use structured reads for those.- Microsoft Fabric ships for structured live reads, but it speaks T-SQL over TDS and is routed through the SQL Server reader rather than the shared warehouse driver — so the SQL console (
run_query) is not available on Fabric.
Time to deliver
| Phase | On Supero | Conventional BI build |
|---|---|---|
| Read-only role + connectivity | ~15 min | ~15 min (same either way) |
| Semantic layer over the tables | ~30 min (bind) | 1–3 weeks |
| Column-level access per role | ~1 h of policy | 2–4 weeks, and again for each BI tool |
| Cost guardrails | 0 — on by default | 1 week, typically after the first surprising bill |
| Dashboards + drill-downs | ~1–2 h | 2–4 weeks |
| Natural-language querying | 0 — the agent ships with it | A separate product and a separate budget |
| Total | ~½ day | ~6 weeks |
When you should just buy a BI tool instead
Be honest about the alternative, because for a lot of teams it is the right one. If what you need is exploration by analysts who already write SQL, dashboards that change weekly, and a semantic layer several teams share, buy the BI tool. It is better at that than this is, and it always will be.
Reach for this instead when the consumers are not analysts. A portal your customers log into, a regional manager who will never open a BI seat, an app that needs warehouse facts alongside its own operational data, an AI agent answering in the asker's own permissions — those are application problems that happen to read from a warehouse, and a BI tool is the wrong shape for all four.
Your warehouse already knows the answers. What it does not have is those answers expressed in your application's roles, a spend ceiling on a query a user typed, an identity per row so a drill-down works, or a UI a regional manager will open.
Build this on Supero
Describe it and get schemas, APIs, SDKs and a governed multi-tenant backend — or point your own AI editor at Supero over MCP and build it there.