rebase.energy

Custom object types

Extend the catalog beyond sites and assets with your own record types and attribute schemas.

sites and assets are the built-in object types. A workspace adds its own: metering points, grid zones, plants, contracts, whatever your data is about. A custom type gets the same generic routes as the built-ins, its records can hold or reference other records, and they carry timeseries exactly like sites do.

Define a type

r = requests.post(f"{BASE}/objects", headers=HEADERS, json={
    "name": "meters",
    "singular": "Meter",
    "plural": "Meters",
    "icon": "🔌",
    "description": "Grid metering points",
    "attributes": [
        {"slug": "ean", "title": "EAN", "type": "text", "required": True},
        {"slug": "voltage", "title": "Voltage level", "type": "select",
         "options": ["LV", "MV", "HV"]},
        {"slug": "zone", "title": "Price zone", "type": "object", "targets": ["zones"]},
    ],
})
r.raise_for_status()
  • name is the URL slug (/objects/meters) and is immutable once created. Built-in slugs and reserved words are rejected. Prefer kebab-case for multi-word slugs.
  • singular and plural are display labels.
  • Each attribute has a slug (the key under values), a title, a type, an optional description, and required (default false). Any slug is legal, because values never shares a namespace with the envelope.

Attribute types

TypeValue under values
textString.
numberNumber.
booleantrue or false.
selectOne of the strings in options.
dateISO 8601 date, for example 2024-06-01.
timestampISO 8601 timestamp with a UTC offset.
geometryA GeoJSON geometry in WGS84 (lon, lat): Point, LineString, Polygon, or their Multi forms. Validated on write and stored verbatim.
objectOne other record. Written as its id, read back as {"id", "name", "object"}.
objectsA list of other records.
betweenExactly the two records each record of this type sits between. One per type.

The last three hold records rather than values. object and objects take targets (the allowed types, or null for any) and a relation: part or reference. Linking records covers them and the between attribute in full. required attributes are enforced on record create.

A geometry attribute is what makes a record mappable and what the GeoJSON export builds features from:

requests.post(f"{BASE}/objects/zones", headers=HEADERS, json={
    "name": "SE3",
    "values": {
        "country": "SE",
        "area": {"type": "MultiPolygon", "coordinates": [[[[15.1, 58.2], [18.9, 58.2], [18.9, 61.0], [15.1, 61.0], [15.1, 58.2]]]]},
    },
}).raise_for_status()

Records of a custom type

Records use the generic routes with the same name plus values shape as every other record. The type's attributes are keys under values.

r = requests.post(f"{BASE}/objects/meters", headers=HEADERS, json={
    "name": "Meter 7071",
    "values": {
        "ean": "735999100000000001",
        "voltage": "MV",
        "zone": se3_id,
    },
})
r.raise_for_status()
meter = r.json()
print(meter["values"]["zone"])
# {"id": "...", "name": "SE3", "object": "zones"}

Unknown keys under values are rejected. An object attribute is written as the target's id; the target must exist (404 otherwise) and be of a declared type (422). List, get, patch, and delete work as described in Objects and records, and on custom types ?filter[<slug>]= narrows the list server-side:

r = requests.get(f"{BASE}/objects/meters", headers=HEADERS,
                 params={"filter[voltage]": "MV", "filter[zone]": se3_id})

Timeseries attach to custom records exactly like to sites:

requests.post(f"{BASE}/objects/meters/{meter['id']}/timeseries", headers=HEADERS, json={
    "type": "consumption",
    "shape": "singleindex",
    "unit": "kWh",
    "points": [{"valid_time": "2024-06-01T00:00:00Z", "value": 412.0}],
}).raise_for_status()

Evolve a type

PATCH /objects/{name} updates the display fields (singular, plural, description, icon) and the attribute list. The slug cannot change.

requests.patch(f"{BASE}/objects/meters", headers=HEADERS, json={
    "attributes": [
        {"slug": "ean", "title": "EAN", "type": "text", "required": True},
        {"slug": "voltage", "title": "Voltage level", "type": "select",
         "options": ["LV", "MV", "HV"]},
        {"slug": "zone", "title": "Price zone", "type": "object", "targets": ["zones"]},
        {"slug": "grid-area", "title": "Grid area", "type": "text"},
    ],
}).raise_for_status()

attributes replaces the whole list

When attributes is present in a PATCH it replaces the type's attribute list entirely, so include every attribute you want to keep, not just the new one.

Records are never rewritten on a type edit. Values of a removed attribute are kept but no longer validated or returned, and re-adding the slug brings them back. Adding a required attribute does not retro-validate existing records. The one attribute that cannot change after create is between, because it decides how the type's records are stored; only its title and description may be edited.

Remove a type

DELETE /objects/{name} removes a custom type. While the type still has records, or while another type's attribute targets it, the request is refused with 409. Pass force=true to delete the records and their attached series first and leave the other types' declarations dangling:

requests.delete(f"{BASE}/objects/meters", headers=HEADERS,
                params={"force": "true"}).raise_for_status()

Built-in types cannot be modified or deleted; attempts return 403.

On this page