Authentication, Users & Permissions API¶
Base paths: /core/api/auth, /core/api/permissions, /core/api/public and the membership routes under /core/api/communities/{community_id}.
This page covers login and token handling (Keycloak-backed), the user administration endpoints, the per-user feature permissions, the
community membership endpoints and the public landing statistics. For the Keycloak token flow itself and how to call the API with a Bearer
token, see Authentication. For the role model (SUPER_USER, ADMIN, MEMBER) and the difference
between a user and a member, see Members. The community configuration endpoints are in the Communities API.
Endpoint summary¶
| Method | Path | Purpose |
|---|---|---|
| POST | /core/api/auth/login |
Exchange email and password for Keycloak tokens |
| POST | /core/api/auth/refresh |
Obtain a new access token from a refresh token |
| POST | /core/api/auth/logout |
Invalidate a refresh token |
| POST | /core/api/auth/signup |
Create a user in Keycloak and in the database (internal, super user) |
| GET | /core/api/auth/me |
Current user profile |
| GET | /core/api/auth/userinfo |
Current user profile (same payload as /me) |
| POST | /core/api/auth/password_reset_request |
Send a password reset email |
| GET | /core/api/auth/users |
List users |
| GET | /core/api/auth/roles |
Current user's platform role |
| PUT | /core/api/auth |
Update the caller's own profile |
| DELETE | /core/api/auth/users/{user_id} |
Delete a user (super user) |
| PATCH | /core/api/auth/users/{user_id}/role |
Change a user's platform role (super user) |
| PATCH | /core/api/auth/users/{user_id} |
Change a user's name or email (super user) |
| POST | /core/api/auth/users/{user_id}/communities/{community_id} |
Link an existing user to a community (super user) |
| DELETE | /core/api/auth/users/{user_id}/communities/{community_id} |
End a user's membership of a community (super user) |
| GET | /core/api/auth/admin/communities |
List every community (super user) |
| GET | /core/api/permissions |
List feature permissions of all users |
| GET | /core/api/permissions/me |
Feature permissions of the current user |
| PUT | /core/api/permissions |
Activate or deactivate a feature permission (super user) |
| GET | /core/api/permissions/ems-modules |
List registered energy management module types |
| GET | /core/api/public/landing-stats |
Public platform totals |
| POST | /core/api/communities/{community_id}/users |
Create a user and add them to a community |
| GET | /core/api/communities/{community_id}/members |
List community members |
| GET | /core/api/communities/{community_id}/members/by-nif |
Find a member by NIF |
| GET | /core/api/communities/{community_id}/members/by-name |
Find a member by name |
| GET | /core/api/communities/{community_id}/members/metrics/kpis |
Savings per member over a period |
| PUT | /core/api/communities/{community_id}/users/{user_id} |
Update a member and the dated membership |
| DELETE | /core/api/communities/{community_id}/users/{user_id} |
End a membership |
| POST | /core/api/communities/{community_id}/users/{user_id}/invite |
Send or resend the dashboard invite |
Who can call what¶
Authorization is enforced by two dependencies (is_allowed and get_current_user) plus explicit checks inside some handlers.
- Authenticated means a valid Keycloak Bearer token (
401if missing, invalid or expired) that maps to a user in the database (404if the user is not found). - Verified means the user's
is_verifiedflag is true.is_allowedrejects unverified users with404 Please verify your account.Users become verified on their first successful login or token refresh. - Global endpoints (no
community_idin the path, for example/core/api/permissions):SUPER_USERmay call everything; any verified user may callGET; every other method isSUPER_USERonly (403). - Community-scoped endpoints (
community_idin the path): the caller must belong to that community (403 You are not a member of this community). CommunityADMINmay use every method; communityMEMBERmay only useGET(403 Community members can only perform read operations).SUPER_USERbypasses these checks. - An unknown
community_idreturns403(the platform'sNotFounderror uses status 403), not404. - Some handlers add a stricter check on top (for example "SUPER_USER only", or "own data only" through
assert_self_or_privileged). Those are stated on each endpoint below.
| Role | Where it lives | Meaning |
|---|---|---|
SUPER_USER |
user.role (platform) |
Unrestricted |
USER |
user.role (platform) |
Default platform role, no implicit community access |
ADMIN |
community membership | Full access inside that community |
MEMBER |
community membership | Read access inside that community |
Authentication¶
Login¶
Exchanges an email and password for Keycloak tokens using the Resource Owner Password (direct grant) flow, and returns the matching database user. On the first successful login the user is linked to its Keycloak ID and marked as verified; pending admin memberships (created by an invitation) are activated at that moment.
Auth / permissions: public (no token).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
username |
body (form) | string | Yes | The user's email address |
password |
body (form) | string | Yes | The user's password |
The body is application/x-www-form-urlencoded (OAuth2 password form), not JSON.
Example
curl -X POST https://<host>/core/api/auth/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin@example.com" \
-d "password=your_password"
Response (abridged)
{
"access_token": "eyJhbGci...",
"refresh_token": "eyJhbGci...",
"token_type": "Bearer",
"expires_in": 300,
"user": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Ana Costa",
"email": "admin@example.com",
"role": { "id": "8c1f0d3e-5a41-4f0b-9a52-0e7f3c2b6d10", "name": "ADMIN" },
"is_verified": true,
"managed_communities": [
{ "id": "6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44", "name": "REC Matosinhos" }
]
},
"role": "ADMIN"
}
Tip
- Invalid credentials return
401 Invalid email or password. A user that exists in Keycloak but not in the RECreation database returns404 User not found in database. Please contact administrator.Any other failure returns500. - The user is matched by email. Direct Keycloak token requests (see Authentication) are also accepted by the rest of the API; this endpoint is the convenient variant that also returns the user and role.
- Access tokens are short-lived (about 5 minutes); renew them with
POST /core/api/auth/refresh.
Refresh Access Token¶
Uses a refresh token to obtain a new access token. Returns the same payload as login (tokens, user, role).
Auth / permissions: public (the refresh token is the credential).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
refresh_token |
body | string | Yes | The refresh token issued by login |
Example
curl -X POST https://<host>/core/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token": "eyJhbGci..."}'
Response (abridged)
{
"access_token": "eyJhbGci...",
"refresh_token": "eyJhbGci...",
"token_type": "Bearer",
"expires_in": 300,
"user": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "email": "admin@example.com", "is_verified": true },
"role": "ADMIN"
}
Tip
- If Keycloak does not rotate the refresh token, the response echoes back the one you sent.
- An invalid or expired refresh token returns
401 Invalid or expired refresh token. A user missing from the database returns404 User not found in database. - When both tokens have expired the user must log in again.
Logout¶
Invalidates the refresh token in Keycloak (the Keycloak session is ended).
Auth / permissions: public.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
refresh_token |
body | string | Yes | The refresh token to invalidate |
Example
curl -X POST https://<host>/core/api/auth/logout \
-H "Content-Type: application/json" \
-d '{"refresh_token": "eyJhbGci..."}'
Response
Tip
The endpoint always answers 200 with this message, even if Keycloak is unreachable or rejects the token (the failure is only logged).
Clients should discard their tokens regardless.
Register User (internal)¶
Creates a user in Keycloak, assigns the matching Keycloak realm role, creates the database user linked through keycloak_id, and creates the
user's feature permissions (all inactive).
Internal endpoint
Declared with include_in_schema=False: it does not appear in the generated OpenAPI documentation. To add members to a community use
POST /core/api/communities/{community_id}/users instead.
Auth / permissions: SUPER_USER only (403 Super users only otherwise).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
name |
body | string | Yes | Full name. The first word becomes the Keycloak first name, the rest the last name |
email |
body | string | No | Email; also used as the Keycloak username |
password |
body | string | Yes | At least 6 characters |
role |
body | enum | No | SUPER_USER, ADMIN (default), MEMBER or USER. Mapped to Keycloak roles SUPER_USER, COMMUNITY_ADMIN, COMMUNITY_MEMBER (anything else maps to COMMUNITY_MEMBER) |
phone |
body | string | No | Phone number |
nif |
body | string | No | Tax identification number, validated |
address |
body | string | No | Address |
Example
curl -X POST https://<host>/core/api/auth/signup \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Ana Costa",
"email": "ana.costa@example.com",
"password": "S3cret-pass",
"role": "ADMIN"
}'
Response 201 Created (abridged)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Ana Costa",
"email": "ana.costa@example.com",
"is_verified": false,
"role": { "id": "8c1f0d3e-5a41-4f0b-9a52-0e7f3c2b6d10", "name": "ADMIN" },
"permissions": [ { "name": "OPTIMIZATION_MODULE", "is_active": false } ]
}
Tip
- If a verified user with that email already exists the call fails with
404 User with this email already exists.An existing unverified user is re-linked to a new Keycloak account instead. - Any failure during creation rolls back the database and returns
400 Failed to create user: .... - The new user is unverified until the first login (
POST /core/api/auth/login).
Get Current User¶
Returns the profile of the authenticated user, including the platform role, feature permissions, managed communities and community roles.
Auth / permissions: any authenticated user (the verified check is not applied here, so unverified users can also call it).
Example
Response (abridged)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Ana Costa",
"email": "ana.costa@example.com",
"phone": "912345678",
"address": "Rua do Porto 123",
"nif": "123456789",
"is_verified": true,
"role": { "id": "8c1f0d3e-5a41-4f0b-9a52-0e7f3c2b6d10", "name": "ADMIN" },
"created_at": "2026-01-15T10:30:00Z",
"ownerships": [],
"permissions": [
{ "id": "0d5a7c34-72a1-4e0e-9f61-6f4b1c2e8a90", "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "OPTIMIZATION_MODULE", "is_active": true }
],
"managed_communities": [
{ "id": "6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44", "name": "REC Matosinhos" }
],
"community_roles": [
{ "id": "6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44", "name": "REC Matosinhos", "role": "ADMIN" }
]
}
Tip
managed_communities lists the communities where the user is ADMIN; community_roles lists every non-deleted community association with
the role in each. Use these to decide which community selector to show in a client. Two routers register GET /core/api/auth/me
(authentication and users); both return the same payload.
Get User Info¶
Returns the database user matching the Keycloak token. In the current implementation the payload is identical to GET /core/api/auth/me.
Auth / permissions: any authenticated user.
Example
Response (abridged)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Ana Costa",
"email": "ana.costa@example.com",
"is_verified": true,
"role": { "id": "8c1f0d3e-5a41-4f0b-9a52-0e7f3c2b6d10", "name": "ADMIN" }
}
Tip
The user is looked up by the token's Keycloak subject first and by email as a fallback; in the fallback case the account is linked to the Keycloak ID on the fly.
Request Password Reset¶
Asks Keycloak to email the user a link to set a new password.
Auth / permissions: public. Rate limited to 20 requests per hour per client IP (429 when exceeded).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
email |
body | string | Yes | The account email |
Example
curl -X POST https://<host>/core/api/auth/password_reset_request \
-H "Content-Type: application/json" \
-d '{"email": "ana.costa@example.com"}'
Response
Tip
The response is always the same, whether or not the email exists or the email could be sent, so the endpoint cannot be used to discover
registered addresses. Only users that already have a Keycloak account (keycloak_id set) actually receive an email. The reset link
redirects to the platform frontend.
Users¶
List Users¶
Returns users of the platform with their role, permissions and community roles.
Auth / permissions: any verified user (global endpoint, GET).
Example
Response (abridged)
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Ana Costa",
"email": "ana.costa@example.com",
"role": { "id": "8c1f0d3e-5a41-4f0b-9a52-0e7f3c2b6d10", "name": "ADMIN" },
"is_verified": true,
"community_roles": [
{ "id": "6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44", "name": "REC Matosinhos", "role": "ADMIN" }
]
}
]
Tip
The handler's description says it excludes SUPER_USER accounts, but the query reads every user in the table, so super users are
included. To list the members of one community use
GET /core/api/communities/{community_id}/members.
Get Current User Role¶
Returns the platform role of the authenticated user.
Auth / permissions: any authenticated user.
Example
Response (abridged)
Tip
The handler returns a single role object while the response model is declared as a list of roles. If your client has trouble with this
route, read role from GET /core/api/auth/me instead, which is the same information in a stable shape.
Update Own Profile¶
Updates profile fields of the authenticated user. Only the fields you send are changed.
Auth / permissions: the route applies is_allowed on a global endpoint with an unsafe method, so in practice only SUPER_USER is accepted
(403 Only super-users can perform this action for anyone else). It always updates the caller's own record, never another user's.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
name |
body | string | No | Full name |
email |
body | string | No | |
password |
body | string | No | New password (see tip) |
nif |
body | string | No | Tax identification number, validated |
address |
body | string | No | Address |
phone |
body | string | No | Phone number |
Example
curl -X PUT https://<host>/core/api/auth \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Ana M. Costa", "phone": "912345678"}'
Response (abridged)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Ana M. Costa",
"phone": "912345678",
"email": "ana.costa@example.com"
}
Tip
- The change is applied to the RECreation database only. It is not synchronised to Keycloak (name, email and password in Keycloak are not
touched). Users who forgot their password should use
POST /core/api/auth/password_reset_request. - To change a user's name or email in both places, a super user can use
PATCH /core/api/auth/users/{user_id}. - To edit a community member's contact details together with the dated membership, use
PUT /core/api/communities/{community_id}/users/{user_id}.
Delete User¶
Permanently deletes a user from the database (permissions, community associations and membership history included) and then removes the Keycloak account.
Auth / permissions: SUPER_USER only (403 Only SUPER_USER can delete users.).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
user_id |
path | UUID | Yes | The user to delete |
Example
curl -X DELETE https://<host>/core/api/auth/users/3fa85f64-5717-4562-b3fc-2c963f66afa6 \
-H "Authorization: Bearer $TOKEN"
Response
Tip
- Deletion is irreversible and invalidates the cached results of every community period the user belonged to. To end a membership while
keeping the user and the history, use
DELETE /core/api/communities/{community_id}/users/{user_id}?effective_at=.... - If the Keycloak removal fails, the database user is already gone; the failure is logged and the call still returns
200. - Unknown
user_idreturns404 User not found.
Change User Role¶
Changes the platform role of a user, in the database and in Keycloak.
Auth / permissions: SUPER_USER only (403 Only SUPER_USER can change roles.).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
user_id |
path | UUID | Yes | The user to change |
role |
body | enum | Yes | SUPER_USER, USER, ADMIN or MEMBER |
Example
curl -X PATCH https://<host>/core/api/auth/users/3fa85f64-5717-4562-b3fc-2c963f66afa6/role \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"role": "ADMIN"}'
Response (abridged)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Ana Costa",
"role": { "id": "8c1f0d3e-5a41-4f0b-9a52-0e7f3c2b6d10", "name": "ADMIN" }
}
Tip
- This changes the user's platform role (
user.role). Per-community roles are changed through the membership endpoints. - A failed Keycloak synchronisation does not roll back the database change; it is only logged, so the two can diverge until the next change.
- Unknown user returns
404; an unknown role name returns400 Invalid role.
Update User (admin)¶
Changes the name and/or email of any user, in the database and in Keycloak.
Auth / permissions: SUPER_USER only (403 Only SUPER_USER can update users.).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
user_id |
path | UUID | Yes | The user to change |
name |
body | string | No | New full name |
email |
body | string | No | New email. Must not be used by another user |
Example
curl -X PATCH https://<host>/core/api/auth/users/3fa85f64-5717-4562-b3fc-2c963f66afa6 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email": "ana.new@example.com"}'
Response (abridged)
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Ana Costa",
"email": "ana.new@example.com"
}
Tip
- Sending neither
namenoremailis a no-op that returns the user unchanged. - Email already used by another user returns
409 Email already in use.; unknown user returns404 User not found. - A failed Keycloak synchronisation is logged and does not roll back the database change.
Link User to Community¶
Links an existing user to a community as MEMBER or ADMIN. The membership is created ACTIVE for the given period. If the user already has
a membership in that community the requested period is replaced with the new role and status.
Auth / permissions: SUPER_USER only (403 Only SUPER_USER can assign communities.).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
user_id |
path | UUID | Yes | The user to link |
community_id |
path | UUID | Yes | The community |
role |
query | enum | No | MEMBER (default) or ADMIN. Other values return 400 Role must be MEMBER or ADMIN. |
start_date |
query | date | Yes | First day (inclusive, Europe/Lisbon) of the membership |
end_date |
query | date | No | Last day (inclusive). Omit for an open-ended membership |
Example
curl -X POST "https://<host>/core/api/auth/users/3fa85f64-5717-4562-b3fc-2c963f66afa6/communities/6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44?role=ADMIN&start_date=2026-01-01" \
-H "Authorization: Bearer $TOKEN"
Response
Tip
- Membership is dated; see Business Validity. Results of the affected period are invalidated and must be recomputed.
is_hostandis_promoterare kept from the covering version when one exists, otherwisefalse.- Unknown user returns
404 User not found.; other errors from the membership write are returned as400with the error message. - This does not create a Keycloak account or send an invitation; use
POST /core/api/communities/{community_id}/users/{user_id}/invite.
Unlink User from Community¶
Ends a user's membership of a community from effective_at. History before that day is kept, and the user account is not deleted.
Auth / permissions: SUPER_USER only (403 Only SUPER_USER can remove community management.).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
user_id |
path | UUID | Yes | The user |
community_id |
path | UUID | Yes | The community |
effective_at |
query | date | Yes | First day with no membership; the membership is closed on the previous day |
Example
curl -X DELETE "https://<host>/core/api/auth/users/3fa85f64-5717-4562-b3fc-2c963f66afa6/communities/6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44?effective_at=2026-07-01" \
-H "Authorization: Bearer $TOKEN"
Response 204 No Content (empty body).
Tip
- Versions that start on or after
effective_atare removed; a version spanning that day is truncated to the day before. - The association is marked
INACTIVEand the community's results fromeffective_atonwards are invalidated. - Returns
404 Association not found.if the user has no membership in the community.
List All Communities (admin)¶
Returns every community in the platform, for the administration screen. This differs from GET /core/api/communities, which only lists
communities the caller belongs to.
Auth / permissions: SUPER_USER only (403 Only SUPER_USER can list all communities.).
Example
Response (abridged)
[
{
"id": "6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44",
"name": "REC Matosinhos",
"identifier": "rec-matosinhos",
"status": "ACTIVE",
"type": "REC",
"market_rate": "VANILLA_IMR",
"version_id": null,
"start_date": null,
"end_date": null,
"total_members": 0
}
]
Tip
total_members is always 0 in this response. Use GET /core/api/communities/{community_id} for aggregated figures. When no community
exists at all the platform's NotFound error is returned, with status 403.
Permissions¶
Feature permissions switch optional modules on or off per user. Each user has one row per PermissionType, created inactive when the user is
created. Permissions are separate from roles.
| Permission | Feature |
|---|---|
OPTIMIZATION_MODULE, SIMULATION_MODULE, SIZING_MODULE, PRICING_MODULE |
Platform modules |
FLEXIBILITY_MODULE |
Umbrella permission for energy management route access. Managed automatically (see the tip on PUT /core/api/permissions) |
EMS_GDBN_BEFLEXIBLE, EMS_OSTEC_SIMPLE, EMS_OSTEC_AC, EMS_OSTEC_FLEX, EMS_OSTEC_FLEX_HEMS, EMS_OSTEC_PROMOTOR, EMS_OSTEC_ARBITRAGEM |
One per registered energy management module, see Energy Management |
List All Permissions¶
Returns the permission rows of all users, except rows belonging to SUPER_USER accounts. Used by the administration screen.
Auth / permissions: any verified user (global endpoint, GET).
Example
Response (abridged)
[
{
"id": "0d5a7c34-72a1-4e0e-9f61-6f4b1c2e8a90",
"user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "OPTIMIZATION_MODULE",
"is_active": true
}
]
Tip
The result is not paginated and not filtered by community: it contains every row of every non-super user (one row per permission type per
user). Filter client-side by user_id.
Get My Permissions¶
Returns the permission rows of the authenticated user, active and inactive.
Auth / permissions: any verified user.
Example
Response (abridged)
[
{ "id": "0d5a7c34-72a1-4e0e-9f61-6f4b1c2e8a90", "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "OPTIMIZATION_MODULE", "is_active": true },
{ "id": "5e1b9a02-3c77-4d18-a4f0-7b6c8d9e0f12", "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "SIZING_MODULE", "is_active": false }
]
Tip
Despite the endpoint description ("active permissions"), inactive rows are returned too. Check is_active before showing a feature.
Update a Permission¶
Activates or deactivates one permission row. The row is located by id; name and is_active of that row are overwritten with the values
sent.
Auth / permissions: SUPER_USER only (global endpoint, unsafe method; 403 Only super-users can perform this action otherwise).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
body | UUID | Yes | The permission row to change |
user_id |
body | UUID | Yes | Owner of the row (required by the schema; the row is located by id) |
name |
body | enum | Yes | Permission type (see the table above) |
is_active |
body | boolean | Yes | New state |
Example
curl -X PUT https://<host>/core/api/permissions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "0d5a7c34-72a1-4e0e-9f61-6f4b1c2e8a90",
"user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "EMS_OSTEC_FLEX",
"is_active": true
}'
Response
{
"id": "0d5a7c34-72a1-4e0e-9f61-6f4b1c2e8a90",
"user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "EMS_OSTEC_FLEX",
"is_active": true
}
Tip
- Changing any
EMS_*permission also updates that user'sFLEXIBILITY_MODULErow: it becomes active if at least oneEMS_*permission is active and inactive when none is. Do not setFLEXIBILITY_MODULEby hand. - An unknown
idis not handled explicitly and results in a server error rather than404.
List EMS Modules¶
Returns the registered energy management module types with their display names. The module_type values correspond to the EMS_<module_type>
permission names.
Auth / permissions: any verified user.
Example
Response (abridged)
[
{ "module_type": "OSTEC_FLEX", "display_name": "OSTEC Flexibility" },
{ "module_type": "OSTEC_ARBITRAGEM", "display_name": "OSTEC Battery Arbitrage" },
{ "module_type": "GDBN_BEFLEXIBLE", "display_name": "GDBN BeFlexible" }
]
Tip
The list comes from the module registry in code, so it reflects what the deployed version supports. Use it to build the per-user permission screen without hard-coding module names.
Public¶
Landing Statistics¶
Returns platform-wide totals, used by the login screen.
Auth / permissions: public (no token, no permission checks).
Example
Response
Tip
Only aggregate counts are exposed. total_members counts every user in the platform (all roles), not only active community members, and
the figures are not scoped to the caller.
Community membership¶
These endpoints sit under /core/api/communities/{community_id} and follow the community-scoped rules in
Who can call what: ADMIN of that community (or SUPER_USER) may write, any member of the community may read.
Membership is dated, see Business Validity and Members.
Add Member to a Community¶
Creates a new user and adds them to the community with a dated membership. The field-by-field description, examples and response are in Communities API: Add Member to Community.
Auth / permissions: community ADMIN or SUPER_USER.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
community_id |
path | UUID | Yes | The community |
name, email, phone, nif, address |
body | string | name required |
Profile of the new user |
status |
body | enum | No | ACTIVE, INACTIVE or DELETED |
role |
body | enum | No | MEMBER (default) or ADMIN |
has_dashboard_access |
body | boolean | No | Create a Keycloak account and send the invitation (needs email) |
is_host, is_promoter |
body | boolean | No | Anchor and Promoter flags |
start_date |
body | date | Yes | First day of the membership |
end_date |
body | date | No | Last day of the membership |
Example
curl -X POST https://<host>/core/api/communities/6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Joao Silva",
"email": "joao.silva@example.com",
"role": "MEMBER",
"status": "ACTIVE",
"has_dashboard_access": true,
"start_date": "2026-01-01"
}'
Response 201 Created (abridged)
{
"id": "9a3c1f7e-2b64-4d85-8e10-5c7d9b0a1e23",
"name": "Joao Silva",
"email": "joao.silva@example.com",
"status": "ACTIVE",
"has_dashboard_access": true,
"is_verified": false
}
Tip
- A password is auto-generated when none is sent; the invited user sets their own through the Keycloak invitation email.
- The Keycloak account and invitation are best effort: if they fail the member is still created and the invitation can be sent again with
the invite endpoint below.
has_dashboard_accessis reported astruewhen the user has a Keycloak ID. - The
rolesent is also stored as the new user's platform role. UseMEMBERorADMIN. - This response carries
statusandhas_dashboard_accessonly; the dated fields (membership_version_id,start_date,end_date,is_host,is_promoter) come back fromGET .../membersandPUT .../users/{user_id}. - When
statusis omitted the membership version is stored asINACTIVE, so sendstatus: "ACTIVE"for the member to take part in market and settlement.
List Community Members¶
Returns the users whose MEMBER membership covers effective_at, ordered by name, with their ownerships and the membership version fields.
See also Communities API: List Community Members.
Auth / permissions: any member of the community (ADMIN or MEMBER), or SUPER_USER.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
community_id |
path | UUID | Yes | The community |
effective_at |
query | date | No | Day to evaluate (default: today, Europe/Lisbon) |
exclude_deleted |
query | string | No | Accepted for compatibility (true/false) |
Example
curl "https://<host>/core/api/communities/6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44/members?effective_at=2026-03-01" \
-H "Authorization: Bearer $TOKEN"
Response (abridged)
[
{
"id": "9a3c1f7e-2b64-4d85-8e10-5c7d9b0a1e23",
"name": "Joao Silva",
"email": "joao.silva@example.com",
"status": "ACTIVE",
"is_host": false,
"is_promoter": false,
"has_dashboard_access": true,
"membership_version_id": "9b2e6c1a-0d4f-4c5e-8a7b-2f1e3d4c5b6a",
"start_date": "2026-01-01",
"end_date": null,
"ownerships": []
}
]
Tip
- Only memberships with the role
MEMBERare returned; community admins are not listed here. exclude_deleteddoes not change the result: membership validity (effective_at) decides who appears. Members whose membership ended beforeeffective_atare absent.ownershipsare evaluated ateffective_atand restricted to this community.- Returns an empty list when nothing is found.
Get Member by NIF¶
Finds a member of the community by tax identification number.
Auth / permissions: community ADMIN or SUPER_USER may look up anyone; a MEMBER may only look up themselves (the nif must equal their
own, otherwise 403 Access denied).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
community_id |
path | UUID | Yes | The community |
nif |
query | string | Yes | Exact NIF to search for |
Example
curl "https://<host>/core/api/communities/6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44/members/by-nif?nif=123456789" \
-H "Authorization: Bearer $TOKEN"
Response (abridged)
{
"id": "9a3c1f7e-2b64-4d85-8e10-5c7d9b0a1e23",
"name": "Joao Silva",
"nif": "123456789",
"status": "ACTIVE",
"is_host": false,
"is_promoter": false,
"has_dashboard_access": true
}
Tip
- No match returns
404 Member not found. - The values reflect the association's current status and flags; the dated fields (
membership_version_id,start_date,end_date) are not populated by this endpoint.
Get Member by Name¶
Finds a member of the community by full name.
Auth / permissions: community ADMIN or SUPER_USER may look up anyone; a MEMBER may only look up themselves (the name must equal their
own, otherwise 403 Access denied).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
community_id |
path | UUID | Yes | The community |
name |
query | string | Yes | Name to search for |
Example
curl -G "https://<host>/core/api/communities/6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44/members/by-name" \
--data-urlencode "name=Joao Silva" \
-H "Authorization: Bearer $TOKEN"
Response (abridged)
{
"id": "9a3c1f7e-2b64-4d85-8e10-5c7d9b0a1e23",
"name": "Joao Silva",
"status": "ACTIVE",
"is_host": false,
"is_promoter": false,
"has_dashboard_access": true
}
Tip
- No match returns
404 Member not found. - Names are not unique; prefer the user
idor the NIF when you have them. URL-encode names with spaces or accents.
Get Members KPIs¶
Returns the savings of each member over a period, computed from the settlement results as the revenue with the community minus the revenue
without it (rev_total - rev_total_no_csc). See Settlement.
Auth / permissions: any member of the community (ADMIN or MEMBER), or SUPER_USER.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
community_id |
path | UUID | Yes | The community |
start_date |
query | date | Yes | Start of the period |
end_date |
query | date | Yes | End of the period |
Example
curl "https://<host>/core/api/communities/6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44/members/metrics/kpis?start_date=2026-01-01&end_date=2026-03-31" \
-H "Authorization: Bearer $TOKEN"
Response (abridged)
{
"9a3c1f7e-2b64-4d85-8e10-5c7d9b0a1e23": {
"savings": 41.27,
"net_consumption": 0,
"co2_reduction": 0
}
}
Tip
- The object is keyed by member (user) ID. Savings are in EUR, summed over the settled days in the period.
net_consumptionandco2_reductionare placeholders and are always0.- Any community member can read the savings of every member through this endpoint, not only their own.
- Only periods that have settlement results contribute; run the settlement first.
Update a Member¶
Updates the member's profile and the dated membership values. Full description in Communities API: Update a Membership.
Auth / permissions: community ADMIN or SUPER_USER.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
community_id |
path | UUID | Yes | The community |
user_id |
path | UUID | Yes | The member |
name, email, nif, address, phone |
body | string | No | Profile fields (not dated) |
status |
body | enum | No | ACTIVE, INACTIVE or DELETED (dated) |
is_host, is_promoter |
body | boolean | No | Anchor and Promoter flags (dated) |
start_date |
body | date | Yes | First day the dated values apply |
end_date |
body | date | No | Last day (inclusive). null = open-ended |
Example
curl -X PUT https://<host>/core/api/communities/6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44/users/9a3c1f7e-2b64-4d85-8e10-5c7d9b0a1e23 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "INACTIVE", "start_date": "2026-07-01"}'
Response (abridged)
{
"id": "9a3c1f7e-2b64-4d85-8e10-5c7d9b0a1e23",
"name": "Joao Silva",
"status": "INACTIVE",
"is_host": false,
"is_promoter": false,
"membership_version_id": "4c8e2a19-7b30-4f56-9d01-3a5b7c9e1f68",
"start_date": "2026-07-01",
"end_date": null
}
Tip
- An email used by another user returns
409(message in Portuguese: "Este email ja esta a ser usado por outro utilizador."). - Sending only profile fields (no
status,is_hostoris_promoter) updates the profile without creating a new membership boundary. - Changing dated fields rewrites the period
[start_date, end_date]and invalidates the community's results for that period. If no membership version coversstart_datethe call fails with the platformNotFounderror (status403). - A member that does not belong to the community also returns the platform
NotFounderror (status403).
End a Membership¶
Ends the membership from effective_at; the user account is kept. See
Communities API: End a Membership.
Auth / permissions: community ADMIN or SUPER_USER.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
community_id |
path | UUID | Yes | The community |
user_id |
path | UUID | Yes | The member |
effective_at |
query | date | Yes | First day without membership |
Example
curl -X DELETE "https://<host>/core/api/communities/6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44/users/9a3c1f7e-2b64-4d85-8e10-5c7d9b0a1e23?effective_at=2026-07-01" \
-H "Authorization: Bearer $TOKEN"
Response 204 No Content (empty body), or 200 with a message when the member still owns resources:
{
"member_id": "9a3c1f7e-2b64-4d85-8e10-5c7d9b0a1e23",
"message": "Membro tem recursos alocados. Por favor, remova-os antes de apagar o membro"
}
Tip
- Ownerships active on
effective_atblock the operation: nothing is changed and the200body above is returned. Remove or end the ownerships first, then repeat the call. Check the status code, not only the absence of an error. - On success the membership versions are closed on the day before
effective_at, the association is set toINACTIVEand the community results fromeffective_atonwards are invalidated.
Send Dashboard Invite¶
Gives a community member access to the dashboard: creates the Keycloak account if the member has none (assigning the realm role and the community group) and sends an email with the link to set the password and verify the email. Calling it again resends the email.
Auth / permissions: community ADMIN or SUPER_USER.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
community_id |
path | UUID | Yes | The community |
user_id |
path | UUID | Yes | The member to invite |
Example
curl -X POST https://<host>/core/api/communities/6b2d9c10-1f4e-4a8b-b7c3-9d5e2a1f0c44/users/9a3c1f7e-2b64-4d85-8e10-5c7d9b0a1e23/invite \
-H "Authorization: Bearer $TOKEN"
Response
Tip
- The member's email is used as the Keycloak username. The Keycloak role is derived from the membership role (
ADMINgivesCOMMUNITY_ADMIN, otherwiseCOMMUNITY_MEMBER). - The Keycloak ID is saved before the email is sent, so a retry after a mail failure does not create a duplicate account.
- No membership in this community returns
404 Member not found in this community; Keycloak or mail failures return502 Failed to send invite: .... - After accepting the invite the member can sign in with
POST /core/api/auth/loginor directly against Keycloak.