Store and read category observations
Keep timestamped provider measurements, such as meter readings, separately from
invoice amounts. A category must already have an active data schema configured
in Ledger. This example expects a schema accepting a numeric
meter_reading_kwh property; adapt the payload to your category's schema.
After configuring the environment, this complete example inspects the schema, stores one observation, reads the latest observation, and walks all records in a bounded time range:
import datetime
import os
from oblidog_client import OblidogClient
with OblidogClient(
base_url=os.environ["OBLIDOG_BASE_URL"],
api_key=os.environ["OBLIDOG_API_KEY"],
) as client:
with client.integrations.run() as run:
schema = client.category_data.schema()
print(schema.version, schema.schema.to_dict())
# Sample provider timestamp and stable reading identity.
observed_at = datetime.datetime(2026, 8, 1, 10, tzinfo=datetime.UTC)
record = client.category_data.create(
observed_at=observed_at,
data={"meter_reading_kwh": 1234.5},
external_id="meter-42-reading-2026-08-01T10:00:00Z",
)
print(record.id, record.source, record.schema_version)
latest = client.category_data.latest()
print(latest.observed_at, latest.data.to_dict())
offset = 0
while True:
page = client.category_data.list(
from_=datetime.datetime(2026, 8, 1, tzinfo=datetime.UTC),
to=datetime.datetime(2026, 8, 31, 23, 59, 59, tzinfo=datetime.UTC),
limit=100,
offset=offset,
)
if not page.data:
break
for observation in page.data:
print(observation.observed_at, observation.data.to_dict())
offset += len(page.data)
run.finish_success(changes_detected=None)
from_ and to are inclusive observation-time filters; the trailing underscore
avoids Python's reserved from keyword. Keep the same bounds while paginating
and advance offset by the number of returned records. Offset pagination is not
a snapshot: concurrent inserts can shift pages. The example reports unknown
changes because a repeated external identity may already exist.
source is assigned by Ledger from the authenticated integration's name; it is
returned on records and is not an argument to create. external_id is an
optional stable provider identity used for idempotency. Reuse it for the same
observation, not for different readings. Omit it when no stable identity exists
(or pass None explicitly); do not assume unidentified repeated submissions
will be deduplicated. Do not generate a fresh random ID for each retry.
The active schema validates submitted data. A schema rejection raises
OblidogValidationError. No active schema or no latest observation can produce
an API error; handle these as setup or empty-history conditions as appropriate
for the returned status. latest() refers to the category's latest observation,
which need not be the record just submitted if newer data already exists.
See category-data reference for models and arguments, and failure handling for a copyable handler.