Tariff API

Manage tariffs, tariff transitions, and tariff items — list, create, read, update, delete, and catalogue operations.

Tariff API Documentation

Overview

The Tariff API provides endpoints for managing tariffs, their successor transitions, and their pricing components (tariff items). Tariffs are associated with organizations and meter types, and can be cloned from a global read-only catalogue. Tariffs can also reference a distribution provider (DSO) and, for time-of-use tariffs, a time-of-use schedule.

Authentication

All tariff endpoints require authentication. Include a valid Bearer token in the Authorization header: Authorization: Bearer <access_token>.

Account scoping

All endpoints require an account_id UUID to scope reads and writes to a specific organization.

  • List / read / delete — pass account_id as a query parameter
  • Create / update — include account_id in the JSON request body

Response format

All endpoints return a unified envelope:

{
  "success": true,
  "message": "optional human readable message",
  "data": {},
  "count": 1,
  "error": "error text"
}

Enum reference

These enums are shared across tariffs, tariff items, and time-of-use scheduling. Individual endpoints may accept a subset of these values — see each endpoint's field reference below.

  • Meter type: ELECTRICITY | WATER | GAS | SOLAR | GENSET | HVAC | CARBON | EV
  • Tariff type: FLAT | TIME_OF_USE | BLOCK | DYNAMIC
  • Currency: ZAR | USD | EUR | GBP | AUD | CAD | JPY | CNY
  • Season: SUMMER | WINTER | TRANSITION (do not use) | ALL_YEAR
  • Tariff item type: TOU_PEAK_ENERGY | TOU_OFF_PEAK_ENERGY | TOU_STANDARD_ENERGY | IMPORT_ENERGY | EXPORTED_ENERGY | ANCILLARY_SERVICE | REACTIVE_ENERGY | NETWORK_CAPACITY | NETWORK_DEMAND | URBAN_LV_SUBSIDY | SERVICE | ADMINISTRATION | RURAL_SUBSIDY | LEGACY | GENERATION_CAPACITY | TRANSMISSION_NETWORK | WATER_CONSUMPTION | CARBON | EV_MIN | EV_SESSION_FEE | AFFORDABILITY | EXCESS_NETWORK_CAPACITY | ELECTRIFICATION_AND_RURAL_SUBSIDY
  • TOU day type: WEEKDAY | SATURDAY | SUNDAY | PUBLIC_HOLIDAY
  • TOU period: PEAK | STANDARD | OFF_PEAK

Field reference

Tariff record (selected fields):

  • id (integer)
  • name (string)
  • description (string|null)
  • account_id (uuid)
  • dso_id (integer|null) — foreign key to the energy provider
  • tou_schedule_id (integer|null) — foreign key to a time-of-use schedule, only set when tariff_type is TIME_OF_USE
  • meter_type (enum, see Enum reference)
  • tariff_type (enum, see Enum reference)
  • currency (enum, see Enum reference)
  • tax_rate (decimal)
  • is_active (boolean)
  • effective_from / effective_to (date)
  • timezone (string) — IANA timezone identifier
  • notes (string|null)
  • cloned_from (integer|null) — source tariff ID when this tariff was cloned from the catalogue

DSO object (nested on tariff reads, null when no provider is set):

  • id (integer)
  • name (string)
  • tz (string) — IANA timezone identifier (e.g. Africa/Johannesburg)

Tariff item record (selected fields):

  • id (integer)
  • tariff_id (integer)
  • item_type_code (enum, see Enum reference)
  • price (decimal)
  • season (enum, see Enum reference)
  • tariff_blocks (array, optional) — nested tiered consumption pricing rows, see Tariff Blocks

Tariff transition record:

  • id (integer)
  • predecessor_id (integer) — the current tariff being replaced
  • successor_id (integer) — the tariff that replaces it
  • effective_from (ISO 8601 timestamp) — when the successor takes over
  • notes (string)
  • created_at (ISO 8601 timestamp)
  • created_by (uuid|null)

Endpoints

Base path: /api/v1/tariffs

List Tariffs

GET /api/v1/tariffs

Query parameters:

  • account_id (uuid, required)
  • page (int, default 1)
  • limit (int, default 10, max 100)
  • query (string, optional — text search)
  • sortBy (string, default created_at)
  • sortOrder (asc | desc, default desc)
  • meter_type (enum: ELECTRICITY | GAS | WATER | CARBON | SOLAR | GENSET | EV, optional)
  • tariff_type (enum: FLAT | TIME_OF_USE | BLOCK, optional)
  • dso_id (int, optional)

Response 200 — list envelope with data array and count.

Create Tariff

POST /api/v1/tariffs

Request body (JSON):

  • account_id (uuid, required)
  • name (string, required)
  • description (string, optional)
  • timezone (string, optional)
  • notes (string, optional)
  • meter_type (enum: ELECTRICITY | GAS | WATER | CARBON | SOLAR | GENSET, optional)
  • tariff_type (enum: FLAT | TIME_OF_USE | BLOCK, optional)
  • dso_id (int, optional)
  • tou_schedule_id (positive integer|null, optional; required for TIME_OF_USE tariffs)
  • effective_from (date string, required)
  • effective_to (date string, required)
  • currency (enum: ZAR, optional)
  • tax_rate (positive integer, optional)
  • is_active (boolean, optional)
  • cloned_from (integer, optional)

Example:

{
  "account_id": "<account-uuid>",
  "name": "Residential Flat Rate",
  "description": "Standard residential plan",
  "meter_type": "ELECTRICITY",
  "tariff_type": "FLAT",
  "tou_schedule_id": null,
  "effective_from": "2026-01-01",
  "effective_to": "2026-12-31"
}

Response 201 — created tariff object in data.

Get Tariff

GET /api/v1/tariffs/:id

Query parameters:

  • account_id (uuid, required)

Returns 200 with the tariff and its nested tariff_items array and dso object.

Update Tariff

PUT /api/v1/tariffs/:id

Request body:

  • account_id (uuid, required)
  • name (string, optional)
  • description (string, optional)
  • timezone (string, optional)
  • notes (string, optional)
  • meter_type (enum: ELECTRICITY | GAS | WATER | CARBON | SOLAR | GENSET, optional)
  • tariff_type (enum: FLAT | TIME_OF_USE | BLOCK, optional)
  • dso_id (int, optional)
  • tou_schedule_id (positive integer|null, optional; required when updating a TIME_OF_USE tariff)
  • effective_from (ISO 8601 string, optional)
  • effective_to (ISO 8601 string, optional)
  • deleted_at (ISO 8601 string, optional)
  • currency (enum: USD | EUR | GBP | ZAR, optional)
  • tax_rate (positive integer, optional)
  • updated_by (uuid, optional)
  • is_active (boolean, optional)
  • cloned_from (integer, optional)

Response 200 — updated tariff object in data.

Delete Tariff

DELETE /api/v1/tariffs/:id

Query parameters:

  • account_id (uuid, required)

Response 204 — no content.

Catalogue guard: Attempts to create, update, or delete a tariff belonging to the catalogue account return 400 (Catalogue is read-only).


Tariff Transitions

Tariff transitions link the current tariff (predecessor) to the next tariff (successor). Each predecessor tariff can only have one successor transition.

When a transition is created or updated with an effective_from timestamp at or before the request time, the API immediately replaces the predecessor tariff with the successor tariff anywhere it is directly assigned to meters or building-block energy dashboard tariff fields. Future-dated transitions are recorded for scheduled tariff replacement.

Base path: /api/v1/tariffs/:id/transitions

Get Transition

GET /api/v1/tariffs/:id/transitions

Path parameter:

  • id (integer) — predecessor tariff ID

Query parameters:

  • account_id (uuid, required)

Returns 200 with the transition in data. Returns 404 if the tariff has no successor transition.

Create Transition

POST /api/v1/tariffs/:id/transitions

Path parameter:

  • id (integer) — predecessor tariff ID

Request body:

{
  "account_id": "<account-uuid>",
  "successor_id": 42,
  "effective_from": "2026-08-01T00:00:00.000Z",
  "notes": "Annual municipal tariff update"
}
  • account_id (uuid, required)
  • successor_id (integer, required) — must belong to the same organization and must differ from id
  • effective_from (ISO 8601 timestamp, required)
  • notes (string, optional)

Response 201 — created transition in data. The data.application object reports whether the transition was applied immediately and how many meter/config references were updated.

Errors: 400 invalid body or self-reference, 404 tariff not found for the account, 409 predecessor already has a successor transition.

Update Transition

PUT /api/v1/tariffs/:id/transitions

Request body:

{
  "account_id": "<account-uuid>",
  "successor_id": 43,
  "effective_from": "2026-09-01T00:00:00.000Z",
  "notes": "Revised effective date"
}
  • account_id (uuid, required)
  • successor_id (integer, optional)
  • effective_from (ISO 8601 timestamp, optional)
  • notes (string, optional)

Response 200 — updated transition in data, including data.application.

Delete Transition

DELETE /api/v1/tariffs/:id/transitions

Query parameters:

  • account_id (uuid, required)

Response 204 — no content.

Catalogue guard: Transitions cannot be managed for catalogue tariffs.


Catalogue (read-only)

The catalogue provides globally maintained, immutable tariff definitions that can be cloned into an organization's scope for customization. Catalogue tariffs and their items cannot be modified directly.

Base path: /api/v1/tariffs/catalogue

List Catalogue Tariffs

GET /api/v1/tariffs/catalogue

Query parameters:

  • page (int, default 1)
  • limit (int, default 10, max 100)
  • query (string, optional — fuzzy match against name/description)
  • sortBy (string, default created_at)
  • sortOrder (asc | desc, default desc)
  • meter_type (enum: ELECTRICITY | GAS | WATER | CARBON | SOLAR | GENSET | HVAC | EV, optional)
  • tariff_type (enum: FLAT | TIME_OF_USE | BLOCK | DYNAMIC, optional)
  • dso_id (int, optional)
  • effective_from_gte (date string, optional)
  • effective_from_lte (date string, optional)

Response 200 — data array of catalogue tariff records with count.

Get Catalogue Tariff

GET /api/v1/tariffs/catalogue/:id

Path parameter:

  • id (integer)

Returns 200 with the catalogue tariff and its nested tariff_items and dso object. Returns 404 if not found.

Clone Catalogue Tariff

POST /api/v1/tariffs/catalogue/:id/clone

Creates a new fully mutable tariff in the destination account, copying all catalogue tariff fields and items.

Path parameter:

  • id (integer) — source catalogue tariff ID

Request body:

{
  "account_id": "<destination-account-uuid>",
  "name": "Optional override name"
}
  • account_id (uuid, required) — destination organization
  • name (string, optional) — falls back to source tariff name

Response 201 — cloned tariff with tariff_items array and dso object.

Errors: 400 invalid body, 404 catalogue tariff not found.


TOU Schedule

Get TOU Schedule

GET /api/v1/tariffs/:id/schedule

Returns the Time-of-Use schedule and its linked rules for a tariff. Only applicable when tariff_type is TIME_OF_USE.

Path parameter:

  • id (integer) — tariff ID

Query parameters:

  • account_id (uuid, required)

Fallback behaviour: When the tariff has no tou_schedule_id configured, schedule ID 3 is used as the default.

Response 200 — schedule object in data:

{
  "success": true,
  "data": {
    "id": 3,
    "name": "Standard TOU Schedule",
    "dso_id": 1,
    "holiday_default": "SUNDAY",
    "is_active": true,
    "tz": "Africa/Johannesburg",
    "version": 1,
    "dso": {
      "id": 1,
      "name": "Example Municipality",
      "tz": "Africa/Johannesburg"
    },
      "seasons": [
        {
          "id": 1,
          "dso_id": 1,
          "label": "SUMMER",
          "date_start": "2026-01-01",
          "date_end": "2026-03-31",
          "tou_rules": [
            {
              "id": 1,
              "schedule_id": 3,
              "season_id": 1,
              "day_type": "WEEKDAY",
              "period": "PEAK",
              "start_minute": 420,
              "end_minute": 540
            }
          ]
        }
      ]
  }
}

tou_schedule fields:

  • id (integer)
  • dso_id (integer)
  • name (string)
  • version (integer) — a DSO/name pair may have multiple versions
  • tz (string) — IANA timezone identifier
  • is_active (boolean)
  • holiday_default (enum, see Enum reference) — day type applied on unlisted holidays

tou_rule fields:

  • id (integer)
  • schedule_id (integer)
  • season_id (integer) — references a DSO season
  • day_type (enum, see Enum reference)
  • period (enum, see Enum reference)
  • start_minute (integer) — minutes from midnight (0–1439), must be a multiple of 30
  • end_minute (integer) — minutes from midnight (exclusive), must be a multiple of 30 and greater than start_minute

The response groups rules under seasons. Each season includes its DSO season fields and a tou_rules array; seasons without rules are returned with an empty array.

Errors:

  • 400 — tariff exists but is not TIME_OF_USE
  • 404 — tariff or TOU schedule not found

Tariff Items

List Items

GET /api/v1/tariffs/:id/items

Query parameters: page, limit, query, sortBy, sortOrder.

Returns paginated tariff_items.

Bulk Create Items

POST /api/v1/tariffs/:id/items

Body may be a single object or an array:

[
  { "price": 100.5, "item_type_code": "IMPORT_ENERGY" },
  { "price": 80.0, "item_type_code": "IMPORT_ENERGY", "season": "WINTER" }
]

Fields:

  • price (decimal, required)
  • item_type_code (enum, see Enum reference, required)
  • season (enum, see Enum reference, optional — defaults to SUMMER)
  • blocks (array, optional) — tiered consumption pricing for this item, used for BLOCK tariffs. Each entry:
    • block_index (integer, required) — 0-based tier ordering
    • upper_limit (decimal|null, optional) — upper consumption bound for the tier; omit or set null for the final, unbounded tier
    • unit_price_cents (integer, optional) — price per unit for consumption within this tier, in cents

A given tariff can only have one item per (item_type_code, season) combination.

Example with blocks:

[
  {
    "price": 0,
    "item_type_code": "IMPORT_ENERGY",
    "season": "ALL_YEAR",
    "blocks": [
      { "block_index": 0, "upper_limit": 600, "unit_price_cents": 359.82 },
      { "block_index": 1, "upper_limit": null, "unit_price_cents": 429.48 }
    ]
  }
]

Response 201 — array of created items, each including its nested tariff_blocks array.

Update Item

PUT /api/v1/tariffs/:id/items/:itemId

Body fields (all optional): price, item_type_code, season.

Response 200 — updated item.

Delete Item

DELETE /api/v1/tariffs/:id/items/:itemId

Response 204 — no content.

Catalogue guard: Any attempt to modify items of a catalogue tariff returns 400 with Catalogue is read-only.


Tariff Item Blocks

Manage the tiered/block consumption pricing rows for a single tariff item. Used when a tariff's tariff_type is BLOCK.

Base path: /api/v1/tariffs/:id/items/:itemId/blocks

List Blocks

GET /api/v1/tariffs/:id/items/:itemId/blocks

Returns the item's blocks in data, ordered by block_index ascending.

Replace Blocks

POST /api/v1/tariffs/:id/items/:itemId/blocks

Replaces the item's entire set of blocks with the provided list (existing blocks for the item are deleted first).

Body — array:

[
  { "block_index": 0, "upper_limit": 600, "unit_price_cents": 359.82 },
  { "block_index": 1, "upper_limit": null, "unit_price_cents": 429.48 }
]
  • block_index (integer, required) — 0-based tier ordering
  • upper_limit (decimal|null, optional) — upper consumption bound for the tier
  • unit_price_cents (integer, optional) — price per unit for consumption within this tier, in cents

Pass an empty array to clear all blocks for the item.

Response 201 — array of created blocks.

Update Block

PUT /api/v1/tariffs/:id/items/:itemId/blocks/:blockId

Body fields (all optional): block_index, upper_limit, unit_price_cents.

Response 200 — updated block.

Delete Block

DELETE /api/v1/tariffs/:id/items/:itemId/blocks/:blockId

Response 204 — no content.

Catalogue guard: Any attempt to modify blocks of a catalogue tariff's items returns 400 with Catalogue is read-only.


Tariff Item Types Endpoint

Read-only reference data listing every tariff item type. There is no create, update, or delete endpoint — this table is maintained centrally.

Base path: /api/v1/tariff_item_types

List Tariff Item Types

GET /api/v1/tariff_item_types

No query parameters or account_id required — this is global reference data, not scoped to an organization.

Response 200 — data array of all tariff item type records (see Tariff Item Types for fields), ordered by code, with count.


The following entities support the tariff data model. They do not currently have dedicated CRUD endpoints under /api/v1/tariffs, but they may appear as nested objects in responses (e.g. the dso object on a tariff or TOU schedule) or otherwise shape tariff behavior.

Tariff Item Types

Reference data describing each item_type_code value used on tariff items.

  • code (enum, see Enum reference) — primary key
  • displayName (string) — human readable label
  • unit (string) — unit of measure for the item (e.g. kWh, kVA, day)

This table is also available as a dedicated read endpoint — see Tariff Item Types below.

Tariff Blocks

Defines tiered/block consumption pricing for a tariff item, used when a tariff's tariff_type is BLOCK. Each row represents one consumption tier for a given tariff item. Managed via the Tariff Item Blocks endpoints, and returned as a nested tariff_blocks array on tariff items in tariff/item reads.

  • id (integer)
  • tariff_item_id (integer) — the tariff item this block belongs to
  • block_index (integer) — ordering of the tier (0-based)
  • upper_limit (decimal|null) — upper consumption bound for this tier; null means unbounded (the final tier)
  • unit_price_cents (integer) — price per unit for consumption within this tier, in cents

A tariff item can only have one block per block_index.

DSO (Distribution System Operator)

Represents the energy provider/utility that a tariff or TOU schedule belongs to.

  • id (integer)
  • name (string, unique)
  • tz (string) — IANA timezone identifier

DSO Season

Defines the date ranges that make up each season for a given DSO. Referenced by tou_rule.season_id.

  • id (integer)
  • dso_id (integer)
  • label (enum, see Enum reference — Season)
  • date_start / date_end (date)

DSO Public Holiday

Per-DSO public holiday calendar. A holiday may override the TOU day type that would otherwise apply on that date.

  • dso_id (integer)
  • on_date (date)
  • name (string)
  • day_type_override (enum, see Enum reference — TOU day type, optional)