Our engine never sees your warehouse password
datahashi

01Product

Define it once. Query it from anywhere.

Each metric is one YAML definition. Every request for it becomes the same correct SQL, with the joins found and the totals right.

model.yaml
entities:
  - name: Order
    table: public.orders
    primary_key: id
    dimensions:
      - name: region
        type: string
    measures:
      - name: revenue
        expr: amount
        agg: sum
      - name: order_count
        expr: id
        agg: count

metrics:
  - name: avg_order_value
    expr: revenue / order_count
    format: currency
A request
{ "metrics": ["avg_order_value"],
  "dimensions": ["region"] }
SQL datahashi writes
SELECT o.region,
       SUM(o.amount) / COUNT(o.id)
         AS avg_order_value
FROM public.orders o
GROUP BY o.region;
result · example values
regionavg_order_value
EMEA118.40
NA131.52
APAC104.77

How it works

Four steps from warehouse to answer

The same four steps whether you self-host or we host.

  1. 01

    Run a Query Runner

    Install it next to your warehouse with a read-only role. It keeps the password and the audit log.

    $ query-runner -config runner.yaml
    ✓ listening :8787
    ✓ warehouse reachable (read-only role)
  2. 02

    Define your metrics

    Write them in YAML. The console checks every definition before anyone can query it.

    metrics:
      - name: avg_order_value
        expr: revenue / order_count
    ✓ 3 metrics valid
    # example
  3. 03

    Publish a version

    Publishing freezes the model and its access rules together. Every change ships as the next version.

    ✓ published v7
      model + access rules, frozen together
    # example
  4. 04

    Hand out access

    Apps get keys and agents sign in with OAuth. Each one sees only the metrics you grant.

    app-finance  key    governed  4 metrics
    agent-sales  OAuth  governed  2 metrics

The compiler

You never write the join

Describe how your tables relate, once. datahashi picks the join path and sums before it joins, so a one-to-many join can't inflate a total.

Order.order_totalmeasure
declared once
Order → Refundone-to-many join
fan-out safe
Refund.reasondimension
order_total by refund reason, all reasons · example data1,284,930✓ no double-counting

Join path resolved from the model. Totals are summed before the join can repeat a row.

As you grow

What stays true at a hundred metrics

Access modes

Narrow for agents, wide for analysts

Governed keys see named metrics only. Explore keys add raw fields for hands-on work. The mode is fixed per key.

named metrics · curated views · the default for agents

  • Ask for metrics · avg_order_value, revenue
  • Query a curated view · finance_summary
  • Ask for raw measures · 403 forbidden
  • Read raw fields · not exposed to this key

Portability

Keep the warehouse you have

Requests never name a warehouse, so you can move later without touching a caller.

  • Snowflake
  • BigQuery
  • Postgres
  • MySQL

Rollups precompute your busiest questions so they skip the warehouse.

Ownership

Every metric has an owner

A definition names the team that owns it. Changes ship as new versions, and a caller can pin the one it trusts.

# model.yaml
- name: Order
  owner: "Finance Analytics"

# a caller pins the version it trusts
{ "metrics": ["avg_order_value"],
  "version": "7" }

Next: how agents ask

An agent sends the same request a dashboard does.