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
| Endpoint | What it does | Key |
|---|---|---|
GET /v1/health | Service health and the latest published release | No |
GET /v1/factors | List 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/estimate | Emissions 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/batch | Up to 1,000 estimates in one call Each item succeeds or fails on its own; results keep the request order. | Required |
GET /v1/releases | Published releases, newest first | Optional |
GET /v1/releases/{version} | A release with its changelog Parameters: version | Optional |
GET /v1/releases/{version}/download | Download 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/keys | Your API keys | Required |
POST /v1/account/keys | Create 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}/revoke | Revoke one of your API keys Takes effect on the key's next request. Parameters: id | Required |
GET /v1/account/usage | Calls 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.tsOr 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
0x00followed 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"