#PowerBI & OData
Helm exposes a read-only OData v4 feed over your
tenant's entities, purpose-built for BI tools — PowerBI's "Get Data → OData
feed" in particular. Every active entity type you can read becomes an entity
set (spaces in the type name become underscores: "Vessel Certificate" ↔
Vessel_Certificate), with id, name, created_at, updated_at and one
column per attribute. Each relationship rule between two readable types surfaces
as a navigation property named after the rule, which PowerBI follows to build a
relational model.
#Why OData?
- Query folding — PowerBI translates filters and sorts into OData query options before hitting the server, so refreshes only pull the rows a report actually needs instead of the whole table.
- Discoverable relationships — the feed's metadata document tells PowerBI how your entity types relate, so multi-table reports don't require manual joins.
- It's the same authenticated, tenant-isolated API surface as everything else in Helm — no separate data-export pipeline to maintain.
#Connect PowerBI
- In PowerBI: Get Data → OData feed, and set the URL to
https://api.helm.bridge-labs.com/entity/odata(see Auth & API Gateway for the staging host). - When prompted, choose Basic authentication:
- User name — the token's
client_id - Password — the token's secret (
helm_tk_…)
- User name — the token's
- Pick the entity sets to import. Each relationship rule is also listed as a
join table named after the rule (e.g.
VesselToVesselCertificate), with one<type>_idcolumn per side — include it whenever you need to relate two entity types in your model. PowerBI discovers the relationships itself from the feed's metadata document.
From there, filter, sort, merge, and visualize as usual — see the PowerBI docs for report-building help.
#Provisioning a token
Mint a Personal Access Token and, for a BI integration, scope it down with a
least-privilege policy rather than handing over your full permissions — see
Provisioning for minting a token
and
Provisioning
for binding a read-only policy to it. For each entity type you want PowerBI to
see, grant read on both entity_type and entity (with the type's id as
resource_id). A relationship rule between two granted types — its join table
and $expand — needs no extra grant. A type without these grants is absent from
the feed, and requesting its entity set returns 404.
The token is revocable at any time (DELETE /access/api-tokens/{tokenId})
without touching the policy or its grants.
#Check the feed
Before wiring up PowerBI, confirm what the token sees with any HTTP client:
# Service document — the entity sets this token can read
curl -u "$CLIENT_ID:$CLIENT_SECRET" https://api.helm.bridge-labs.com/entity/odata
# Metadata document — column types and navigation properties (XML)
curl -u "$CLIENT_ID:$CLIENT_SECRET" 'https://api.helm.bridge-labs.com/entity/odata/$metadata'
# One entity set, filtered, sorted and paged
curl -u "$CLIENT_ID:$CLIENT_SECRET" -G https://api.helm.bridge-labs.com/entity/odata/Vessel \
--data-urlencode "\$filter=startswith(name,'MV')" \
--data-urlencode '$orderby=created_at desc' \
--data-urlencode '$top=50' --data-urlencode '$count=true'
A collection response is { "@odata.context", "value": [...] }, plus
@odata.count when $count=true and @odata.nextLink when more rows remain.
GET /entity/odata/{EntitySet}({id}) returns one row, and
GET /entity/odata/{EntitySet}({id})/{rule} its related rows.
#Supported query options
PowerBI Import mode drives the feed with:
$filter—eq ne gt ge lt le,and or not, parentheses,contains/startswith/endswith, andnull/true/false/ISO-datetime literals. Only indexed attributes (see Entities & Data Model) and entity-level fields (id,name,created_at,updated_at) are filterable — filtering an un-indexed attribute returns400.$orderby— attributes indexed withsortable: true, pluscreated_atandupdated_at. Ordering by any other column returns400.$select,$top,$skip,$count.$skipmust be a multiple of$top(a400otherwise); for anything else follow@odata.nextLink.$expand— a relationship rule's related entities nest inline in the response ($expand=<rule name>).- Server-driven paging via
@odata.nextLink— PowerBI follows it automatically on refresh, so you don't need to configure page size.
$apply (server-side aggregation) is not supported and returns 501 —
PowerBI's Import mode aggregates locally, so bring the data in with
$filter/$orderby/$top and aggregate in the report.
#Gotchas
#Relationship cardinality
The feed's metadata tells PowerBI which columns relate two entity types, but not whether the relationship is one-to-one, one-to-many, or many-to-many — PowerBI infers it from the data it sees on first import. If the initial data happens to have at most one related row per row, PowerBI may guess one-to-one; a later refresh that sees multiple related rows then errors on duplicates. Fix it by editing the relationship's cardinality in PowerBI's model view.
#Ambiguous (circular) relationships
If two entity types are related through more than one relationship rule (e.g. a "Vessel" relates to "User" via both an owner rule and an administrator rule), PowerBI may report an ambiguous-relationship error when you build a visualization. Deactivate whichever relationship isn't relevant to that report — see PowerBI's relationship troubleshooting guide.