Reference

SAAVI Insights API v1

version 1.0.0 · base URL https://insights.saavi.com.au/api/public/v1 · read-only · machine contract at /api/public/v1/openapi.json

Quick start

All endpoints live under https://insights.saavi.com.au/api/public/v1 over HTTPS. Ask SAAVI for a key for your organisation, then confirm connectivity and freshness before wiring up the dashboard.

# 1. Confirm the key works and see how fresh the data is
curl -s https://insights.saavi.com.au/api/public/v1/health \
  -H "X-Api-Key: $SAAVI_API_KEY"

# 2. Read the rules your figures are calculated under
curl -s https://insights.saavi.com.au/api/public/v1/organization \
  -H "X-Api-Key: $SAAVI_API_KEY"

# 3. Pull the KPI set the dashboard needs
curl -s "https://insights.saavi.com.au/api/public/v1/metrics/summary?period=day&from=2026-08-01&to=2026-08-28" \
  -H "X-Api-Key: $SAAVI_API_KEY"

An hourly refresh needs three calls: /metrics/summary, /metrics/top-customers and either /orders or the CSV export. That is well inside the per-key rate limit.

Authentication

Every request carries an organisation API key in the X-Api-Key header (a Bearer token is also accepted). Keys are shown once at creation and stored only as a SHA-256 hash, so a leaked key can be revoked but never recovered. A key is bound to exactly one organisation, may be restricted to a subset of customers, and grants only the scopes issued to it.

curl -s "https://insights.saavi.com.au/api/public/v1/metrics/summary?period=day" \
  -H "X-Api-Key: saavi_live_..."

Scopes:

  • metrics:read
  • orders:read
  • exports:read
  • webhooks:manage
  • bridge:write

Response envelope

Success and failure share one shape, so a dashboard needs a single parser. Every response reports how fresh the data is, and whether it is live or synthetic sandbox data.

{
  "data": { "...": "endpoint payload" },
  "meta": {
    "correlation_id": "7f3c...",
    "timestamp": "2026-02-11T04:00:12.031Z",
    "version": "1.0.0",
    "data_as_of": "2026-02-11T03:00:00.000Z",
    "freshness_seconds": 3612,
    "data_quality": "live"
  }
}

Endpoints

MethodPathScopePurpose
GET/api/public/v1/healthnoneLiveness plus data freshness for the presented key.
GET/api/public/v1/organizationmetrics:readReporting rules in force: GST basis, week start, cancelled-order handling, pallet basis.
GET/api/public/v1/metrics/summarymetrics:readSales value, order count, pallets, average order value and unique customers, with prior-period comparison and the bucket series.
GET/api/public/v1/metrics/top-customersmetrics:readCustomers ranked by sales value across the window, with share of window sales.
GET/api/public/v1/ordersorders:readPaginated order history.
GET/api/public/v1/orders/{orderId}orders:readOne order with its lines.
GET/api/public/v1/exports/orders.csvexports:readCSV export of order history for spreadsheet or ETL consumers.
GET/api/public/v1/webhookswebhooks:manageList webhook subscriptions.
POST/api/public/v1/webhookswebhooks:manageCreate a webhook subscription. The signing secret is returned once.
DELETE/api/public/v1/webhookswebhooks:manageDeactivate a webhook subscription.
GET/api/public/v1/bridge/ingestbridge:writeSync watermarks and last run status, for the on-prem bridge runner.
POST/api/public/v1/bridge/ingestbridge:writePush a batch of SAAVI rows into the mirror; the final batch recomputes rollups and fires webhooks.

GET /api/public/v1/metrics/summary

  • period day|week|month Bucket size. Defaults to day.
  • from date Inclusive start (YYYY-MM-DD). Defaults to a period-appropriate look-back.
  • to date Inclusive end (YYYY-MM-DD). Defaults to today.

GET /api/public/v1/metrics/top-customers

  • period day|week|month Defaults to month.
  • from date Inclusive start.
  • to date Inclusive end.
  • limit integer 1-100 How many customers. Defaults to 10.

GET /api/public/v1/orders

  • from date Inclusive start. Defaults to 30 days ago.
  • to date Inclusive end.
  • limit integer 1-500 Page size. Defaults to 50.
  • offset integer Rows to skip. Defaults to 0.
  • customer_code string Restrict to one customer code.
  • include_cancelled boolean Include cancelled orders.

GET /api/public/v1/orders/{orderId}

  • orderId integerrequired SAAVI cart id.

GET /api/public/v1/exports/orders.csv

  • from date Inclusive start.
  • to date Inclusive end.
  • limit integer 1-20000 Row cap. Defaults to 5000.

DELETE /api/public/v1/webhooks

  • id uuidrequired Subscription id.

Periods and timeframes

Pass period=day|week|month with optional from and to dates. Buckets are built in the organisation's timezone, using the configured week start day, so a weekly figure matches what the operations team counts as a week.

GET /api/public/v1/metrics/summary?period=week&from=2026-01-01&to=2026-02-11
GET /api/public/v1/metrics/top-customers?period=month&limit=10

Metric definitions

  • Sales value — summed order value on the organisation's GST basis (ex or inc). Cancelled orders are excluded, included, or reported separately according to the organisation's rule; the rule in force is returned by /organization and echoed in metric responses.
  • Number of orders — distinct order headers whose order date falls in the bucket.
  • Average order value — sales value divided by order count for the same bucket. Never averaged across buckets.
  • Pallets — pallet equivalents derived from order lines. The derivation basis is stated with every figure, because pallet counts differ by product configuration.
  • Top customers — ranked by sales value over the requested window, with each customer's share of window sales.

Rate limits and freshness

Default limit is 120 requests per minute per key, which comfortably covers an hourly dashboard refresh with room for ad-hoc drill-downs. Limits are per key, so a scheduled job and an interactive dashboard can hold separate keys and never starve each other.

The mirror refreshes hourly. The freshness target is 3600s; every response reports data_as_of and freshness_seconds so a dashboard can show its own staleness rather than silently displaying old numbers.

Error codes

CodeHTTP
UNAUTHORIZED401
FORBIDDEN_SCOPE403
RATE_LIMITED429
CUSTOMER_NOT_ALLOWED403
INVALID_PERIOD400
INVALID_RANGE400
INVALID_INPUT400
NOT_FOUND404
STALE_DATA409
UPSTREAM_UNAVAILABLE503
INTERNAL_ERROR500

Webhooks

Subscribe an HTTPS endpoint and receive a callback when a refresh completes, so the dashboard pulls only when there is new data. The signing secret is returned once at creation.

Event types:

  • metrics.rollup.completed
  • orders.ingested
  • sync.failed
curl -s -X POST https://insights.saavi.com.au/api/public/v1/webhooks \
  -H "X-Api-Key: saavi_live_..." \
  -H "content-type: application/json" \
  -d '{ "target_url": "https://dashboard.example.com/hooks/saavi",
        "event_types": ["metrics.rollup.completed"],
        "description": "Ops dashboard" }'


# Each delivery is signed:
X-Saavi-Timestamp: 1770787212
X-Saavi-Event-Id: 6f0c...        # stable across retries — de-duplicate on it
X-Saavi-Signature: sha256=<hex HMAC of "<timestamp>.<raw body>">

Verify by recomputing the HMAC-SHA256 over timestamp + "." + rawBody with your signing secret, comparing in constant time, and rejecting timestamps older than five minutes. Deliveries retry with backoff and are logged. An endpoint that fails 15 times in a row is deactivated.

Scheduled exports

If a pull-based CSV feed suits an existing tool better, the same figures are available as a CSV endpoint that Google Sheets, Power Query or an ETL job can fetch on a schedule with the key in the header.

curl -s "https://insights.saavi.com.au/api/public/v1/exports/orders.csv?from=2026-01-01&to=2026-02-11" \
  -H "X-Api-Key: saavi_live_..." -o saavi-orders.csv