Skip to content

Developers

API v1

Read your data.Send us readings.Hear what changes.

Everything your own systems need: read-only tokens for your BI tool or data warehouse, keys for site equipment that sends readings, and signed webhooks for your security monitoring. Download the OpenAPI description.

1 · Start

An admin makes a token in Settings, API tokens, and chooses what it can read. Send it as a bearer token to your Snowleaf address followed by /api/v1. Every answer is JSON, except files and CSV exports.

Your first request
export SNOWLEAF=https://your-snowleaf-address   # where you sign in
export SNOWLEAF_TOKEN=sla_…                       # Settings → API tokens

curl "$SNOWLEAF/api/v1/sites" \
  -H "Authorization: Bearer $SNOWLEAF_TOKEN"

2 · Tokens and scopes

A token only reads, and only what its scopes allow. It always expires, within a year, and an admin can revoke it at once and see when it was last used. Settings, billing, members and keys can never be reached with one, whatever its scopes.

read:data
Sites, sources, meters, readings, uploads, manual entries, energy prices, tenants, runs and their results, anomaly flags, emission factors and method versions.
read:reports
Reports, their PDFs and evidence packs, and the fixed-schema export.
read:audit
The audit log, its chain check and its export. Report plan and up.

3 · Sandbox

Try the API before your own data is in: Snowleaf Sample is a demo organisation with a full year of synthetic hourly readings for one site (grid, a diesel generator and rooftop solar), with its runs, reports and audit log. Sandbox tokens read it with every scope.

Email hello@snowleaf.xyz for a sandbox token. It lasts 30 days, and nothing in the sandbox can be changed, so every answer stays the same as you build against it.

4 · Reading data

25 endpoints, grouped by the scope that reads them. Paths are relative to /api/v1.

With read:data

  • GET/emission-factorsEvery emission factor Snowleaf's method uses, current and past, with where each comes from
  • GET/method-versionsThe calculation engine's available method versions
  • GET/runsEvery calculation run your organisation has made, across every site
  • GET/runs/{runId}Get a calculation run and its hourly results
  • GET/runs/{runId}/anomaliesStretches of this run's readings that look wrong by the engine's rules, and whether someone has checked each
  • GET/runs/{runId}/export/calculation.csvOne row per hourly result, the detail a report's figures are built from
  • GET/runs/{runId}/export/tenants.csvThe tenant split by month, one row per tenant per month
  • GET/runs/{runId}/tenantsThe run's emissions split by tenant, by their share of IT energy each hour; empty when the site had no tenant meters
  • GET/sitesEvery site in your organisation
  • GET/sites/{siteId}/energy-pricesThe site's energy prices, newest first within each kind
  • GET/sites/{siteId}/manual-entriesA site's bills and fuel logs, newest period first
  • GET/sites/{siteId}/metersList a site's meters
  • GET/sites/{siteId}/readingsEvery reading for one day in the site's calendar (the latest day with readings when day is left out), older versions included, with which one calculations use and the file each came fromQuery: day (optional)
  • GET/sites/{siteId}/sourcesList a site's sources
  • GET/sites/{siteId}/tenantsA site's tenants
  • GET/sites/{siteId}/uploadsEvery file uploaded for a site, newest first, with how many of its rows are still in use
  • GET/sites/{siteId}/uploads/{uploadId}/fileThe original file exactly as it was uploaded

With read:reports

  • GET/reportsEvery report your organisation has made, across every site
  • GET/runs/{runId}/export/fixed-schema.csvThe run's results in a fixed CSV layout, for loading into other carbon-accounting platforms
  • GET/runs/{runId}/reportA report version for a run (the latest unless version is given), rendered as a printable pageQuery: version (optional)
  • GET/runs/{runId}/report/evidence-packA ZIP with the report, the readings and original uploads it used, its factors, method, audit extract, checksums and a script that re-runs the totalsQuery: version (optional)
  • GET/runs/{runId}/report/pdfA report version as a PDF, regenerated from the stored versionQuery: version (optional)

With read:audit

  • GET/audit-eventsThe organisation's audit log, newest first (admin, approver or auditor)Query: before (optional), entityType (optional), limit (optional)
  • GET/audit-events/exportEvery audit event in a period as CSV or JSON, with its hashes, for the organisation's own recordsQuery: format, from, to
  • GET/audit-events/verifyRe-hash the organisation's whole audit chain and report the first break, if any

5 · Pushing readings

A building management system, gateway or script can send a site's hourly readings with a site key, made by an admin on the site's Upload tab. Each push is checked like an uploaded file, kept exactly as received, and safe to repeat: send the same Idempotency-Key and you get the first answer back.

Send an hour of grid readings
curl -X POST "$SNOWLEAF/api/v1/ingest/readings" \
  -H "Authorization: Bearer snw_…" \
  -H "Idempotency-Key: ups-2026-03-02T09" \
  -H "Content-Type: application/json" \
  -d '{"readings":[{"entityType":"source","entityName":"Grid","hour":"2026-03-02T09:00:00+01:00","quantity":61.5,"unit":"kWh"}]}'

Up to 10,000 readings or 5 MB a push, and 720 pushes an hour per key. A later reading for the same hour replaces the earlier one in every calculation, and both are kept.

6 · Webhooks

An admin adds an https address in Settings, Webhooks, and chooses the audit log, alerts or both. Each event is posted within a minute, signed with the endpoint's secret. Anything but a 2xx answer within 10 seconds is retried after 1, 5 and 30 minutes, then 2 and 12 hours, and can be sent again by hand.

What arrives
POST /your/endpoint
Snowleaf-Event: audit.event
Snowleaf-Delivery: 6f0c…            # the same on every retry of this delivery
Snowleaf-Signature: t=1792928856,v1=0f56…

{
  "id": "audit:68",
  "type": "audit.event",            // or alert.opened, alert.resolved, test
  "createdAt": "2026-10-25T11:46:52Z",
  "organisationId": "8fbc…",
  "data": { "sequence": 68, "action": "site.created", "hash": "…", "previousHash": "…", … }
}
Check the signature in Node
import crypto from 'node:crypto';

// body is the raw request body, exactly as received; secret is whsec_… from Settings → Webhooks.
export function isFromSnowleaf(body, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; // older than five minutes
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${body}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ''));
}
Or in Python
import hashlib, hmac, time

def is_from_snowleaf(body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:  # older than five minutes
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Audit events carry their hash and the one before, so your system can check the chain as events arrive. Use the delivery id to ignore a repeat.

7 · Limits and errors

Requests
Up to 300 a minute per token. Past that, 429 with Retry-After in seconds.
Errors
A JSON body with one readable sentence: {"error": "…"}. 401 means a token unknown, revoked or expired; 403, a scope or route the token can’t reach; 404, nothing of yours with that id.
Times
ISO 8601 with an offset. Readings are hourly; a period’s end is exclusive.
Money
Whole kobo, as integers.

8 · Versions and changes

Every path starts /api/v1. Adding an endpoint or a field isn't a breaking change, so ignore fields you don't know. Removing or changing one is, and is announced here and to every admin at least six months ahead.

25 Oct 2026
First public version: API tokens with scopes, the endpoints below, pushing readings with site keys, and signed webhooks for the audit log and alerts.