API docs

The Mizani Carbon API serves released, source-traceable factors over HTTPS as JSON. Numbers are decimal strings, so values stay exactly as released. Errors use RFC 9457 problem details. The full OpenAPI document is at /openapi.json, with interactive docs at /docs.

Quickstart

Factor lookups for the latest release are public. Estimates need a key: get an API key and send it as Authorization: Bearer mzn_live_….

curl

curl https://mizani-api.fly.dev/v1/factors/ke.electricity.grid.location_based?date=2025-09-15

curl -X POST https://mizani-api.fly.dev/v1/estimate \
  -H "Authorization: Bearer $MIZANI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "factor_key": "ke.fuel.diesel.stationary", "amount": "100", "unit": "litre", "date": "2025-09-15", "gwp": "ar6" }'

Python

import os, requests

r = requests.post(
    "https://mizani-api.fly.dev/v1/estimate",
    headers={"Authorization": f"Bearer {os.environ['MIZANI_API_KEY']}"},
    json={
      "factor_key": "ke.fuel.diesel.stationary",
      "amount": "100",
      "unit": "litre",
      "date": "2025-09-15",
      "gwp": "ar6"
    },
)
r.raise_for_status()
result = r.json()
print(result["co2e_kg"], "kg CO2e")
print(result["citation"])

JavaScript

const res = await fetch("https://mizani-api.fly.dev/v1/estimate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MIZANI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "factor_key": "ke.fuel.diesel.stationary",
    "amount": "100",
    "unit": "litre",
    "date": "2025-09-15",
    "gwp": "ar6"
  }),
});
const result = await res.json();
console.log(result.co2e_kg, "kg CO2e", result.citation);

Units convert only within a dimension (kWh, MWh, GWh; g, kg, t; ml, litre, m3). Asking for diesel in kWh returns 422, never a silent conversion. The grid factor is published as CO₂ only, so its co2e_kg is null; use gases_kg.co2.

API reference

EndpointWhat it doesKey
GET /v1/healthService health and the latest published releaseNo
GET /v1/factorsList factors in a release
Filter by region, sector, key prefix, the date a factor is valid on, and release (default: latest).
Parameters: region (optional), sector (optional), key_prefix (optional), date (optional), release (optional), gwp (optional), page (optional), per_page (optional)
Optional
GET /v1/factors/{factor_key}The factor valid on a date, with full provenance
Returns one row per activity unit. Where published periods overlap (EPRA's July–December and financial-year values), the shortest period covering the date is returned.
Parameters: factor_key, date (optional), gwp (optional), release (optional)
Optional
POST /v1/estimateEmissions for an amount of activity
Multiplies the amount by the released factor valid on the date. Returns kg CO2e on the chosen GWP basis, each gas, biogenic CO2 separately, the factor used, and a citation to paste into a report.
Required
POST /v1/estimate/batchUp to 1,000 estimates in one call
Each item succeeds or fails on its own; results keep the request order.
Required
GET /v1/releasesPublished releases, newest firstOptional
GET /v1/releases/{version}A release with its changelog
Parameters: version
Optional
GET /v1/releases/{version}/downloadDownload a release as CSV or XLSX
Every factor in the release with its provenance and citation. The XLSX adds README, Sources and Changelog sheets. Public for the latest release; older releases need a key. The ETag is the file's SHA-256.
Parameters: version, format (optional)
Optional
GET /v1/releases/{version}/proof/{factor_id}Prove a factor belongs to a release
A Merkle inclusion proof from the factor's CSV row to the release fingerprint. Hash the leaf, apply each step, and compare with merkle_root (and with the on-chain attestation, if there is one). Public for the latest release; older releases need a key.
Parameters: version, factor_id
Optional
GET /v1/sources/{id}A source document: publisher, title, URL, retrieval date and SHA-256
Parameters: id
Optional
GET /v1/account/keysYour API keysRequired
POST /v1/account/keysCreate an API key
The full key is in this response only. Store it now: only its hash is kept.
Required
POST /v1/account/keys/{id}/revokeRevoke one of your API keys
Takes effect on the key's next request.
Parameters: id
Required
GET /v1/account/usageCalls per day across your keys
Parameters: days (optional)
Required

For AI agents

MCP server

The Mizani Carbon Model Context Protocol server gives an agent three read-only tools: search_factors finds factors by words such as “diesel” or “paraffin”, get_factor returns a factor with its sources, pages and citation, and estimate turns an amount into kg of each gas and kg CO₂e. Every answer carries the citation. Lookups work without a key; estimate needs one.

It runs on Node 22.18 or later. It isn't on npm yet: pilot customers get it from us. Add it to Claude Code:

claude mcp add --transport stdio   --env MIZANI_API_KEY=mzn_live_…   --env MIZANI_API_URL=https://mizani-api.fly.dev   mizani -- node /path/to/mizani/mcp/bin/mizani-mcp.ts

Or to Claude Desktop (Settings, Developer, Edit Config), in claude_desktop_config.json:

{
  "mcpServers": {
    "mizani": {
      "command": "node",
      "args": ["/path/to/mizani/mcp/bin/mizani-mcp.ts"],
      "env": {
        "MIZANI_API_KEY": "mzn_live_…",
        "MIZANI_API_URL": "https://mizani-api.fly.dev"
      }
    }
  }
}

Pay per call (x402)

Not available yet. It stays switched off until a legal review of Kenya's virtual-asset rules is complete, and will start on a test network.

Verify a release

You don't have to trust us that a release is unchanged. Each release has a fingerprint: a Merkle root over the rows of its CSV download, published with the release and, once attested, on a public blockchain (the Ethereum Attestation Service on Base). The attestation holds only fingerprints and file hashes, never factor values.

The method, to reimplement in any language:

  • Leaf: SHA-256 of the byte 0x00 followed by the row's values as a compact JSON array of strings, in the CSV's column order (UTF-8).
  • Node: SHA-256 of 0x01, then the left child, then the right child. Leaves are in the file's row order; an unpaired last node moves up a level unchanged.

In Python, with only the standard library:

import csv, hashlib, json, sys

def sha(*parts):
    return hashlib.sha256(b"".join(parts)).digest()

with open(sys.argv[1], newline="", encoding="utf-8") as f:
    header, *rows = list(csv.reader(f))

level = [sha(b"", json.dumps(r, separators=(",", ":"), ensure_ascii=False).encode("utf-8"))
         for r in rows]
while len(level) > 1:
    level = [sha(b"", level[i], level[i + 1]) if i + 1 < len(level) else level[i]
             for i in range(0, len(level), 2)]
print(level[0].hex())  # compare with the release's fingerprint

Compare the result with fingerprint.merkle_root from GET /v1/releases/{version}, and with the attestation linked on the changelog. To check a single factor without the whole file, GET /v1/releases/{version}/proof/{factor_id} returns its row and the sibling hashes up to the root.

Google Sheets

Two functions bring factors into a spreadsheet. =MIZANI(factor_key, amount, unit, date) returns kg CO₂e, and =MIZANI_CITE(factor_key, date, [unit]) returns the citation to paste into your report.

=MIZANI("ke.fuel.diesel.stationary", B2, "litre", C2)
=MIZANI("ke.electricity.grid.location_based", B3, "kWh", C3, "co2")
=MIZANI(A2:A500, B2:B500, C2:C500, D2:D500)
=MIZANI_CITE("ke.fuel.diesel.stationary", C2, "litre")
  • An optional fifth argument picks one gas: co2, ch4, n2o, hfc or biogenic_co2. The grid factor is published as CO₂ only, so use "co2" for electricity. A sixth argument, "ar5", switches the GWP basis.
  • Ranges fill a whole column in one call. If a row fails, the range shows the error and its row number, so a total never silently leaves a row out.
  • Mizani Carbon › Pin to a release keeps a report's numbers fixed when a new release is published.

Install

  1. In your spreadsheet, open Extensions › Apps Script.
  2. Replace Code.gs with the Mizani Carbon script, and appsscript.json with its manifest (Project Settings › Show "appsscript.json"). Pilot customers get both from us.
  3. Reload the spreadsheet. In the new Mizani Carbon menu, set the API address to https://mizani-api.fly.dev, then your API key, then choose Test connection.

The key is stored in the spreadsheet's script, where anyone who can edit the spreadsheet can read it. Give a shared spreadsheet its own key.