Skip to Content
Conventions

Conventions

Every operation follows the same rules, so once you know them you can read any response without surprises.

Versions and compatibility

The major version is the first segment of the path: /v3/…. Within a major version, changes are only additive: new operations, new optional parameters, new response fields and new enum values. Anything that would break an integration (removing or renaming something, changing a type, unit, default, rounding or meaning, or adding a required parameter) ships as a new major version instead.

So that additions never break you:

  • Ignore fields you don’t recognise. Don’t fail on them, and don’t reject a response because it has more than you expected.
  • Accept enum values and codes you don’t know. Handle them like a generic case rather than as an error.

When a major version is deprecated, every response from it carries a Deprecation and a Sunset header, and the sunset is at least six months after the announcement. See Migrate from v1 and v2 for the current one.

Responses

A successful response is always a JSON object with data, and meta where there is something to say about the delivery:

{ "data": { "…": "the resource, the list or the computed view" }, "meta": { "…": "delivery facts, such as how current data is" } }

data is self-describing: everything that changes what a number means (its unit, the period it covers, how it was aggregated, what was left out) is inside data, next to the number. When you store or forward a result, keep data whole.

Errors use a different format, described in Errors and limits.

Ids

Ids are strings, such as "1042". Treat them as opaque: compare them for equality, but don’t parse them, convert them to numbers, sort by them or assume a length or format. Today they look like numbers; that may change.

Fields and missing values

  • Every documented field is always present. A field that doesn’t apply, or isn’t known, is null; the reference says which fields can be.
  • An empty list is [], never null or missing.
  • null and 0 mean different things. 0 is a measured zero; null means there is no data. For example, a category with no data points in a month is null, while a category whose data adds up to nothing is 0.

Dates and times

KindFormatExample
TimestampRFC 3339, UTC, with Z2026-09-16T09:12:04.000Z
DateYYYY-MM-DD2025-03-31
MonthYYYY-MM2025-03

Timestamp fields end in At, apart from meta.asOf. Ranges include both ends unless an operation says otherwise: from=2025-01&to=2025-12 is the whole of 2025.

Units and numbers

  • Every quantity names its unit, in a field named after the measure: emissions comes with emissionsUnit. Read the unit from the response, never from what you asked for.
  • Money is an object with a decimal amount and an ISO 4217 currency: { "amount": 1500.5, "currency": "NOK" }.
  • Percentages run from 0 to 100, and their names end in Percent.
  • Rounding. Values are rounded once, from unrounded data, to the precision the reference gives for each field. So a total can differ slightly from the sum of its rounded parts: three categories of 0.333 t each are returned as 0.33 + 0.33 + 0.33 = 0.99, while their total of 0.999 t is returned as 1.00. Both are right. Don’t adjust either to make them match.

Enums and codes

  • Enum values are UPPER_SNAKE_CASE, in responses and in query parameters alike: granularity=QUARTER, SCOPE_1, UP_TO_DATE. Values from external standards keep their own form: units (kg, tCO2e), ISO 4217 currencies (NOK) and ISO 3166 countries (NO).
  • Values that only the server chooses can gain new members at any time (see Versions and compatibility). The reference lists the known ones.
  • Where a value comes as a code with a label, the code is stable: it is never renamed, removed or reused. The label is for display only and can change at any time. Program against code, never label.

Requests

  • Send request bodies as JSON, with Content-Type: application/json.
  • Unknown parameters are rejected with a 400, in the query and in the body alike. A misspelled filter fails loudly instead of being ignored.
  • An input the API can’t honour exactly is rejected with a 400 that says what would be accepted. It is never silently adjusted: a range that doesn’t line up with the requested period size is an error, not rounded to one that does.

Lists

Every list response has meta.nextCursor. null means you have the whole list.

No list is paginated yet, so nextCursor is always null today. Once a list is paginated, it will return a cursor, and you request the next page by sending it back as cursor, until nextCursor is null. Write that loop now and your integration won’t need to change. Send cursor only when you received one: today, lists accept no query parameters, and an unknown one is a 400.

import os import requests BASE_URL = "https://api.energi.ai" HEADERS = {"Authorization": f"Bearer {os.environ['ENERGI_API_KEY']}"} def get_all(path): """Every item of a list, following meta.nextCursor.""" items, params = [], {} while True: response = requests.get(f"{BASE_URL}{path}", headers=HEADERS, params=params, timeout=60) response.raise_for_status() page = response.json() items.extend(page["data"]) cursor = page["meta"]["nextCursor"] if cursor is None: return items params = {"cursor": cursor} companies = get_all("/v3/company")
import os import requests BASE_URL = "https://api.us.energi.ai" HEADERS = {"Authorization": f"Bearer {os.environ['ENERGI_API_KEY']}"} def get_all(path): """Every item of a list, following meta.nextCursor.""" items, params = [], {} while True: response = requests.get(f"{BASE_URL}{path}", headers=HEADERS, params=params, timeout=60) response.raise_for_status() page = response.json() items.extend(page["data"]) cursor = page["meta"]["nextCursor"] if cursor is None: return items params = {"cursor": cursor} companies = get_all("/v3/company")

Each list documents the order it returns its items in.

Last updated on