Recipes
The other pages are organised by endpoint. This one is organised by task — the things integrations actually get asked to do. Each recipe states the goal, the shape of the solution, and the trap to avoid.
All examples assume:
AUTH=(-H "Authorization: Bearer $TOKEN" -H "Api-Version: 1.7")
API=https://api.opinum.com
Export a month of consumption for many delivery points
Goal — one daily total per delivery point, for a whole month.
Resolve the sources by EAN, collect their variable ids, then ask for a single aggregated query rather than a series per meter.
# 1. The sources, by EAN
curl -s "${AUTH[@]}" "$API/sources?Ean=541448000000000000&DisplayLevel=Light"
# 2. Their consumption variables
curl -s "${AUTH[@]}" "$API/variables?SourceIds=101&SourceIds=102&DisplayLevel=Normal"
# 3. One query, daily totals, all variables at once
curl -s "${AUTH[@]}" "$API/data?VariableIds=501&VariableIds=502&VariableIds=503\
&DisplayLevel=ValueVariableDateSource\
&Granularity=Day&Aggregation=SUM\
&From=2026-08-01T00:00:00&To=2026-08-31T23:59:59&IncludeToBoundary=true\
&UseReportingTimezone=true"
Important
UseReportingTimezone=true is what makes "a day" mean the site's day. Without it, a daily total is cut on
UTC midnight, which is not where the invoice cuts it.
Past a few dozen variables, move the filter into the body with POST /data — see
When the filter is too long for a URL.
Detect silent meters
Goal — list the variables that have received nothing recently.
Ask the server to count, instead of downloading series and counting yourself.
curl -s "${AUTH[@]}" "$API/data?VariableIds=501&VariableIds=502&VariableIds=503\
&DisplayLevel=ValueVariableDateSource\
&Granularity=All&Aggregation=COUNT\
&From=2026-08-21T00:00:00Z&To=2026-08-28T00:00:00Z&IncludeToBoundary=true"
Golden rule
The silent meters are the ones missing from the answer, not the ones returning zero.
A variable with no data in the window is simply absent from the response. Reconcile the ids you requested against the ids you got back — reading only the returned rows makes the failures invisible, which is exactly backwards for this recipe.
requested = {501, 502, 503}
returned = {row["variableId"] for row in response.json()}
silent = requested - returned
Tip
Insights has a built-in feature for this — see the dataLossDetection and dataQuality endpoints, and
the corresponding User Manual pages. Build your own only if you need the result inside your own system.
Get the last known reading of every meter
Goal — a dashboard tile showing the latest value per variable.
A paging trick, not a dedicated endpoint:
curl -s "${AUTH[@]}" "$API/data?VariableIds=501&VariableIds=502\
&DisplayLevel=ValueVariableDateSource\
&Paging.PageNumber=0&Paging.ItemsPerPage=1&PagingOrder=DESC"
Create a source only if it does not exist
Goal — an idempotent synchronisation, safe to re-run.
Look up by a business identifier, then create or update:
GET /sources?SerialNumber=DEMO-0001&DisplayLevel=Normal
├── empty → POST /sources (create)
└── found → compare, then PUT /sources if anything differs
Golden rule
Compare before writing, and report created / updated / unchanged separately.
A synchronisation that blindly PUTs everything on every run cannot tell you what actually moved, and turns a quiet night into thousands of pointless writes. Three counters at the end of the run are what make the job observable.
Tip
POST /sources returns the full source object; POST /sites and POST /variables return a bare
integer id. Handle the two shapes explicitly.
Copy the settings of a template source
Goal — every new source should look like a reference one.
Read the template, carry over the fields that define its behaviour, then create:
curl -s "${AUTH[@]}" "$API/sources?Id=999&DisplayLevel=Verbose"
Typically carried over: sourceTypeId, energyTypeId, energyUsageId, gatewayTypeId, timeZoneId, and
the form fields that classify the source. Never carried over: id, siteId, and the business identifiers.
Note
Keeping a template source in the account is more robust than keeping the same values in your code: someone can adjust it in the interface without a deployment.
Correct a bad batch of data
Goal — a range was pushed with wrong values; replace it.
1. DELETE /data with WhatIf=true → read what would go
2. Check the count against what you expect
3. DELETE /data with WhatIf=false → actually delete
4. Push the corrected points
Important
Step 1 is not optional. A VariableIds list with one id too many is indistinguishable from a correct one
until the data is gone — see Deleting data points.
Find every variable that feeds a calculated variable
Goal — understand what a calculation depends on before changing it.
curl -s "${AUTH[@]}" "$API/variables?CalculatedFilter.ImpactingVariableId=501&DisplayLevel=Verbose"
Returns the calculated variables impacted by variable 501 — the dependency direction that matters when you are about to modify or delete an input. See Calculated variables.
Attach a file and have it processed
Goal — push a CSV export and let the mapper turn it into data points.
curl -s -X POST "${AUTH[@]}" \
"$API/storage?Filename=readings-2026-08-28.csv" \
-F "file=@readings-2026-08-28.csv;type=text/plain"
The file is processed only if its name matches an active trigger. See Files and triggers.
Tag a subset of sources for an integration
Goal — let the business decide which sources your integration handles, without a deployment.
Tag the sources in the interface, then filter on the tag:
curl -s "${AUTH[@]}" "$API/sources?Tags=my-integration&DisplayLevel=Light"
Tip
This is almost always better than a CustomFilter in your code, and better than a list of ids in your
configuration. The selection becomes visible and editable by the people who own it — see
Selecting by view, by tag or by custom filter.
Run a nightly job safely
Goal — a scheduled integration that does not wake anyone up.
| Concern | What to do |
|---|---|
| Token | One token for the whole run, cached until expires_in. |
| Referentials | Resolved once at start-up — see Reference data. |
| Batching | Around 80 ids per URL, or POST /data. |
| Retries | Bounded, with a delay, on timeouts and 5xx only. Never on 4xx. |
| Idempotency | Re-running the job must not duplicate anything. |
| Reporting | Counters at the end: read, created, updated, unchanged, failed. |
| Push feedback | An operationId you choose, so a webhook or a support ticket ties back to the run. |
Note
The operationId query parameter on the push endpoint is free-form. Something like
NIGHTLY-2026-08-28-<uuid> costs nothing and is worth a great deal the day you need to trace one run among
thousands.