DocsIntegrations

Integrations

Warehouse and BI handoff patterns

4 min readReviewed July 2026

GraphJSON is optimized for interactive event analysis, dashboards, alerts, and embedded results. A warehouse or lakehouse is usually a better home for broad cross-company modeling, long-term raw history, regulatory reporting, and large transformation dependency graphs.

Use a deliberate handoff when a governed GraphJSON calculation must join a larger reporting workflow. Do not treat the Data API as an undocumented raw event export service.

Choose one of four patterns#

Pattern Use when
Scheduled aggregate extract A bounded GraphJSON result belongs in a warehouse or BI model
Customer-controlled dual write Raw replayable history or large event-level analysis is required
Warehouse-to-GraphJSON publication The warehouse calculates a stable aggregate needed in GraphJSON
Direct BI request A low-volume internal tool can safely call a fixed Data API report

Avoid circular pipelines in which a result is exported, transformed, written back, and then becomes an undocumented input to its own definition.

Pattern 1: scheduled aggregate extract#

Use the Data API with a fixed, versioned SQL query:

const reports = {
  daily_active_accounts_v3: {
    sql_query: `
      SELECT
        toDate(toDateTime(timestamp)) AS day,
        uniqExact(JSONExtractString(json, 'account_id'))
          AS active_accounts
      FROM product_events
      WHERE
        JSONExtractString(json, 'event') = 'core_workflow_completed'
        AND timestamp >= toUnixTimestamp(toDateTime('2026-07-25 00:00:00'))
        AND timestamp < toUnixTimestamp(toDateTime('2026-07-26 00:00:00'))
      GROUP BY day
      ORDER BY day
    `,
    graph_type: "Table"
  }
};

Execute it from a trusted scheduler:

async function loadReport(report) {
  const response = await fetch(
    "https://api.graphjson.com/api/visualize/data",
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        api_key: process.env.GRAPHJSON_API_KEY,
        ...report
      }),
      signal: AbortSignal.timeout(30_000)
    }
  );

  const body = await response.json();

  if (!response.ok) {
    throw new Error(body.error || `GraphJSON returned ${response.status}`);
  }

  return body.result;
}

Keep the report name, SQL, result schema, time zone, maturity delay, and owner in source control.

Design the output contract#

The warehouse table should not depend on a visualization's incidental labels.

Example:

dataset: product_daily_active_accounts
grain: UTC day
columns:
  day Date
  active_accounts UInt64
  source_window_end DateTime
  report_version String
  extracted_at DateTime
  extraction_run_id String
primary key:
  day + report_version
maturity:
  wait 24 hours after day end
restatement:
  rewrite the last 3 days
owner:
  Product Analytics

Validate types before loading. Reject unexpected columns rather than allowing a BI table to drift silently.

Use immutable extraction runs#

For every attempt, record:

extraction_run_id
report key and version
source window
started and completed time
row count
checksum or aggregate controls
status
error class

Load into staging, validate, then merge into the final table with an idempotent key. A retry must replace or reproduce the same logical snapshot rather than append another copy.

Freeze reporting windows#

Relative ranges such as "30 days ago" are convenient for live dashboards but weak extraction contracts. Resolve exact boundaries:

window_start = 2026-07-25T00:00:00Z
window_end   = 2026-07-26T00:00:00Z

Wait for the documented late-arrival period. Restate a small trailing interval when late events are expected, and record that policy.

Respect Data API boundaries#

The SQL result is capped at 2,000 rows. Design aggregate datasets that remain well below that limit.

The Data API is appropriate for:

  • daily or hourly governed metrics
  • compact segment tables
  • bounded account-level operational lists
  • versioned forecast or target outputs

It is not a documented path for:

  • every raw event in a workspace
  • unbounded row-level replication
  • full backup or disaster recovery
  • streaming change capture
  • high-frequency BI queries with arbitrary SQL

For raw history, use a source-controlled durable archive and dual-write pipeline.

Pattern 2: customer-controlled dual write#

transactional outbox or durable event log
        ├─ GraphJSON consumer
        ├─ warehouse/lake consumer
        └─ raw archive consumer

Each consumer owns its checkpoint, retries, and reconciliation. One destination's success does not prove another is complete.

Use this pattern when:

  • event-level history must outlive GraphJSON retention
  • rebuilds must not depend on GraphJSON export
  • the warehouse needs a different schema or enrichment
  • contractual recovery requirements apply

Do not send a transaction to GraphJSON first and assume it can always become the warehouse recovery source later.

Pattern 3: publish warehouse results into GraphJSON#

When the warehouse calculates a stable aggregate needed for a GraphJSON dashboard, publish one versioned fact per grain:

{
  "event": "governed_metric_published",
  "event_id": "net_revenue:2026-07-25:v4",
  "metric_key": "net_revenue",
  "metric_version": 4,
  "window_start": "2026-07-25T00:00:00Z",
  "window_end": "2026-07-26T00:00:00Z",
  "value_minor": 4829100,
  "currency": "usd",
  "source_model": "finance.daily_net_revenue",
  "source_run_id": "run_20260726_04",
  "completeness": "final",
  "schema_version": 1
}

Use deterministic event identity and explicit restatement rules. Do not mix preliminary and final results without a status field.

Follow Derived events and scheduled rollups.

Pattern 4: direct BI request#

A small internal BI tool may call a fixed Data API report through a trusted server. Apply the same controls as embedded analytics:

  • report allowlist
  • no arbitrary browser SQL
  • server-side API key
  • fixed result schema
  • private cache by authorization scope
  • timeouts and failure states
  • query-volume monitoring

Do not configure a desktop BI client with a shared workspace key if every user can reveal or reuse it.

Reconcile the handoff#

For every settled window compare:

GraphJSON result row count
staging row count
final table row count
sum or distinct-count controls
window bounds
report version
latest successful extraction time

For additive measures, compare totals. For non-additive distinct or percentile metrics, compare the published result itself and a checksum of the dimension keys; do not sum daily distinct counts into a monthly distinct count.

Handle corrections#

Choose one policy:

  • overwrite the same report-version key
  • append a new result version and select the latest
  • publish correction deltas with explicit links

Do not silently mutate a final finance or contractual report. The warehouse governance process should decide whether a correction is allowed and how consumers see it.

GraphJSON remains analytical, not the accounting authority.

Secure the workflow#

  • keep the API key in the scheduler or server secret manager
  • restrict destination credentials separately
  • avoid customer-level rows unless required
  • redact query and response logging
  • classify generated extracts
  • expire temporary files
  • isolate test and production
  • keep report ownership current

If the dataset contains personal or customer-confidential data, apply the same deletion and access controls in both systems.

Handoff checklist#

  • The warehouse and GraphJSON responsibilities are explicit.
  • The selected pattern matches raw, aggregate, or reverse-publication needs.
  • SQL, schema, grain, time zone, maturity, and owner are versioned.
  • Extraction windows use exact half-open boundaries.
  • Loads are staged, validated, and idempotent.
  • The result stays within the Data API row boundary.
  • Raw archival does not depend on the Data API.
  • Reverse-published metrics carry source and calculation versions.
  • Direct BI requests use fixed server-owned reports.
  • Counts and metric controls reconcile on settled windows.
  • Corrections have a documented restatement policy.
  • Keys, extracts, logs, and destination access are protected.
Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support