Docs
Using the Carbon Data API
Every dataset is a set of static files behind one base URL. There is nothing to install and no key to request.
Quickstart
The catalog lists the datasets; each dataset's metadata links to its files.
curl -s "https://api.carbondata.dev/v1/catalog"
curl -s "https://api.carbondata.dev/v1/emission-factors"
curl -sO "https://api.carbondata.dev/v1/emission-factors/download/emission-factors.csv" URL scheme
All paths start at https://api.carbondata.dev. Data paths end in a format extension.
| Path | What it returns |
|---|---|
| /v1 | The API index: site name and links. |
| /v1/catalog | Every dataset on the site with its current version and coverage. |
| /v1/status | Freshness of every dataset (see the status page). |
| /v1/{dataset} | Metadata: version, coverage, source, license, validation and downloads. |
| /v1/{dataset}/versions | The current version, previous versions and monthly snapshots. |
| /v1/{dataset}/locations | Series ids with their location ids, and location names. For tables: the partitions. |
| /v1/{dataset}/{series}/{location}.json | One series at one place (also .csv). |
| /v1/{dataset}/table/{partition}.json | Rows of a table for one partition value, or table/all for tables up to 50,000 rows (also .csv). |
| /v1/{dataset}/download/{dataset}.csv | The whole dataset in one file (also .parquet). |
| /v1/{dataset}/{path} | A file of a files dataset, at the path its metadata lists. |
The API reference lists the exact URLs for every dataset on this site.
IDs
Dataset and series ids are lowercase and kebab-case. Location ids follow one scheme across all the sites:
| Place | Format | Example |
|---|---|---|
| The world | world | world |
| Country | ISO 3166-1 alpha-3, lowercase | usa, twn |
| US state or equivalent | us- and the USPS code | us-ca, us-dc, us-pr |
| US county | us-county- and the 5-digit FIPS code | us-county-06037 |
| Station or monitor | source prefix and the source's id | usgs-01646500, aqs-060371103 |
Formats
JSON
A series file has a meta object and a data array ordered by date; this sketch uses National fossil CO2 emissions:
{
"meta": {
"dataset": "national-fossil-co2",
"series": "total",
"series_name": "Total fossil CO2 emissions",
"location": "gcb-international-aviation",
"location_name": "International aviation (bunker fuels)",
"units": { "value": "MtCO2" },
"source": "…", "license": "…", "attribution": "…"
},
"data": [
{ "date": "YYYY", "value": <number> },
…
]
} A table file has meta and a rows array ordered by the key columns, as in Emission factors:
{
"meta": {
"dataset": "emission-factors",
"key": ["factor_id"],
"partition": { "source": "…" }, // absent in table/all
"rows": <integer>,
"units": { },
"source": "…", "license": "…", "attribution": "…"
},
"rows": [
{ "factor_id": <string>, "source": <string>, "source_version": <string>, "source_ref": <string> or null, "scope": <string>, "category": <string>, "activity": <string>, "region": <string>, "region_type": <string>, "activity_unit": <string>, "gas": <string>, "value": <number>, "unit": <string>, "gwp_set": <string>, "valid_year": "YYYY", "notes": <string> or null },
…
]
}
Missing values are null, never sentinel numbers. Dates are strings: YYYY-MM for months,
YYYY-MM-DD for days, YYYY for years and ISO 8601 UTC ending in Z for
times.
CSV
UTF-8, a header row, comma-separated, \n line endings, and an empty field for a missing value. The same columns as the JSON.
Parquet
Zstandard-compressed with column statistics, so tools such as polars and DuckDB can read only the row groups they need over HTTP range requests. Bulk Parquet files are usually a fraction of the size of the CSV.
Pinned versions
Each published update is a version with an id like 2026-10-23.3f9a1c2e4b: the publish date and the
start of a hash of every file. Plain URLs always serve the current version. Put a version after the dataset id
to pin it:
curl -s "https://api.carbondata.dev/v1/emission-factors/versions"
# the same data, frozen at one version
curl -s "https://api.carbondata.dev/v1/emission-factors@2026-10-23.3f9a1c2e4b" /v1/{dataset}@{version}/...works for every path of a dataset: metadata, series, tables and downloads.- Pinned responses never change and are cached as immutable for a year.
- The first version of each month is a snapshot kept permanently: cite those in papers and reports. Other versions stay available for at least 30 days, then are removed unless they are the current version or one of the 10 before it.
/versionslists the current version, recent previous ones and one snapshot per month.- A version that failed validation is never served. Asking for it returns unknown-version.
Caching and ETags
- Every data file has an
ETag: the SHA-256 of its content. Send it back inIf-None-Matchand an unchanged file returns304 Not Modifiedwith no body. - Current-version data URLs are cached for 5 minutes for datasets that update more than every 2 hours, 30 minutes for those updating up to every 2 days, and an hour otherwise, with
stale-while-revalidate=86400, stale-if-error=604800. - Pinned URLs send
public, max-age=31536000, immutable. - The catalog and dataset metadata are cached for 5 minutes, and the status for 1 minute.
X-Data-Versionnames the version that answered,Last-Modifiedis when it was published, andLink: rel="describedby"points to the metadata.- Range requests (
Range: bytes=…) return206 Partial Content.
curl -si "https://api.carbondata.dev/v1/emission-factors/download/emission-factors.csv" | grep -i etag
# etag: "3f9a…" (the SHA-256 of the file)
curl -si -H 'If-None-Match: "3f9a…"' "https://api.carbondata.dev/v1/emission-factors/download/emission-factors.csv"
# HTTP/2 304 (nothing downloaded; your copy is current) Errors
Errors are RFC 9457 problem documents with
Content-Type: application/problem+json. The type URL points to an explanation, and
most add a hint with the request to make instead.
{
"type": "https://carbondata.dev/docs/errors#unknown-file",
"title": "Not found",
"status": 404,
"detail": "table/nowhere.json is not part of emission-factors version 2026-10-23.3f9a1c2e4b.",
"instance": "/v1/emission-factors/table/nowhere.json",
"request_id": "8d1f0c2b9e7a4f31-IAD",
"hint": "GET /v1/emission-factors/locations"
} Every error type, with what causes it and how to fix it.
Rate limits and API keys
Without a key you can make 120 requests every 60
seconds from one IP address. Past that, the API answers 429 with a
rate-limited problem and Retry-After: 60.
API keys are coming, with higher limits. Until then everyone is anonymous. For large pulls, download the bulk CSV or Parquet file once instead of requesting thousands of small files, and use ETags so unchanged files cost nothing.
CORS
Any website can call the API from the browser. Responses allow every origin for GET, HEAD, OPTIONS,
accept the Authorization, X-API-Key, If-None-Match, Range request headers, and expose
ETag, Last-Modified, X-Data-Version, Link, Content-Range, Accept-Ranges, Retry-After.
Licenses and credit
The data keeps its source's license. Each dataset page, and the sources page, gives the
license, whether commercial use is allowed and the exact credit line to use. Every JSON file repeats the license
and attribution in its meta.