API Reference
All BFF endpoints are served under /api. The JWT is read from the
X-Auth-Request-Access-Token header (injected by oauth2_proxy), falling back to
Authorization: Bearer. The token is verified in full — see
Architecture; every endpoint below answers 401 without a
valid one.
Interactive docs at http://localhost:8014/api/docs, OpenAPI schema at
http://localhost:8014/api/openapi.json.
User
GET /api/me
Returns the authenticated user's profile, terms acceptance status, and settings.
POST /api/terms/accept
Record the user's acceptance of the current terms version.
Overview
GET /api/overview
Returns the energy overview for the authenticated user's community. Aggregates four separate Digital Twin fetches into the member's totals, the community's totals, and a daily trend.
Query parameters:
- days: relative range in days when no custom dates are provided, default 7, maximum 365.
- start_date and end_date: inclusive custom date range in YYYY-MM-DD format, maximum 1 year. Both must be provided together.
Responses:
- 400 — dates supplied singly, reversed, in the future, or spanning more than a year.
- 404 — the caller is not a participant, or has no community membership.
Individual upstream failures degrade rather than fail. If the member's meter data
cannot be fetched, the response is still 200 with null figures in that block and the
community block populated; the trend always contains one entry per day in the range,
filled or null. A dashboard with gaps is preferred to no dashboard.
Weather
GET /api/weather
Returns current weather conditions for the user's community location via the Digital Twin.
Forecast
GET /api/forecast
Returns energy production/consumption forecast for the user via the Digital Twin.
Community
GET /api/community
Returns community metadata (name, description, legal and contact details, links) from the rec-registry.
Always 200. If the registry is unreachable, misconfigured, or rejects the token, the
response falls back to {"key": "unknown", "name": "REC"} rather than an error — the
three cases are indistinguishable to the caller and are separated only in the logs.
Suggestions and Commitments
GET /api/suggestions
List active flexibility window suggestions for the user. Includes current window details, acceptance status, and available actions.
POST /api/suggestions/{suggestion_id}/remind
Schedule a flexibility reminder for a suggestion via the nudging-tool.
POST /api/suggestions/{suggestion_id}/respond
Accept or reject a flexibility suggestion. Creates a commitment in the flexibility-api.
DELETE /api/commitments/{commitment_id}
Cancel an active commitment.
Gamification
GET /api/gamification
Returns the user's gamification profile: season points, level, badges, accepted-action count and anonymous season ranking.
Points come from the Digital Twin's rec_points_leaderboard and rec_participant_points
fetchers — not from the flexibility-api. The flexibility-api's reward_points_actual
is deliberately not used, because its settlement formula does not compare against baseline
and so inflates the value.
Two modes, and the response does not label which one produced it:
- Season — a usable leaderboard row supplies total_points, the season_* fields and
ranking. The level ladder is 100 points per level applied to season points, so it
resets each season.
- Fallback — when that row is unavailable or malformed (older Digital Twin deployment,
device not yet in the fleet, brand-new device), total_points is the all-time sum of
daily points, every season_* field is null, and ranking is null.
GET /api/gamification/history
Returns the user's commitment history from the flexibility-api.
CO2 Settings
GET /api/settings/co2
Returns CO2 emission factors and configuration.
Settings
Settings are split across two owners and merged into one object here: simple_mode,
font_scale and webpush_enabled are stored by this service, while the notification
limit, the email channel and the per-kind catalogue belong to the nudging-tool.
GET /api/settings
Return the merged settings.
Query parameters:
- lang — language for the notification-kind catalogue. Falls back to Accept-Language.
Only it, en and es are recognised; anything else is dropped.
Responses:
- 502 — notification preferences could not be loaded. Settings are not rendered
half-known.
- A catalogue that fails to load degrades quietly instead: 200 with kinds: [].
PUT /api/settings
Update settings, writing each half to its owner. Returns the merged result.
Only kinds marked enabled are sent upstream. Kinds with editable: false (such as
extr_event) are not a member's to switch off and keep that flag across the round trip.
Responses:
- 422 — the body could not be validated. detail is a plain string suitable for
display, such as Email address format is invalid, not an array of error objects.
- 502 — the nudging-tool rejected or could not accept the update.
Notifications
GET /api/notifications
List notifications for the authenticated user, from the nudging-tool.
Takes no query parameters — the upstream client's limit, offset and unread_only
are not exposed here and its defaults apply. severity is normalised to critical,
warning or info; any other value the upstream introduces collapses to info.
POST /api/notifications/enable
Enable notifications for the user.
POST /api/notifications/disable
Disable notifications for the user.
POST /api/notifications/read-all
Mark all notifications as read.
POST /api/notifications/{notification_id}/read
Mark a single notification as read.
GET /api/notifications/webpush/vapid-public-key
Return the VAPID public key for web push subscription setup.
POST /api/notifications/webpush/subscribe
Register a browser push subscription endpoint.
POST /api/notifications/webpush/unsubscribe
Remove a push subscription.
Data sharing
Behind DATA_SHARING_ENABLED, off by default. While the flag is off — or while the
identity registry or connector URL is unset — every route here answers 404 and the app
hides the section, so nothing half-working is exposed.
Every call is made with the member's own verifiable credential. This service only resolves which credential is theirs; it cannot act on their behalf, and neither can an administrator.
GET /api/data-sharing
Every published offer, merged with this member's decision on it.
Offers are read from the published vocabulary on each request and never cached or vendored: two copies of the text somebody agrees to is how the thing displayed and the thing recorded drift apart. If the vocabulary is unreachable the call fails rather than serving a stale copy.
has_identity: falsewith an empty list — a member with no dataspace identity. A normal state, not an error.503— the dataspace is unreachable.
POST /api/data-sharing/{offer_id}
Grant or withdraw one offer. Body: {"enabled": true | false}.
Withdrawal is the reason this route exists. The onboarding wizard can only grant, so without it a consent could be given and never taken back — a compliance defect rather than a missing feature. Anything that reworks this surface must keep withdrawal reachable independently of the wizard.
409— the account has no dataspace identity, or the offer is contract-based and therefore disclosed rather than toggled.503— the dataspace is unreachable.
GET /api/data-sharing/history
What has happened with this member's data, served by provenance under their own credential. Absent provenance returns an empty history rather than an error.
Feedback
POST /api/feedback
Submit user feedback. Returns 201 on success.
Health
GET /health
Service health check.