Tenant Roles & Organisation Best Practices
This guide shows a recommended default model you can seed into each tenant to support multi‑level structures such as Platform Owner → Distributor → Reseller → Operator → Customer → End User.
Use this as a starting point. Tenants may rename roles and adjust permissions to match their governance.
1) Organisation Hierarchy Model
Within a tenant, create an organisation tree that mirrors the commercial chain. Example:
Root (Tenant)
├─ Distributor: Acme Distributors
│ ├─ Reseller: Voltify UK
│ │ ├─ Customer: GreenFleet Ltd
│ │ │ ├─ Site: Depot A
│ │ │ └─ Site: HQ Car Park
│ └─ Reseller: ZapCo Ireland
└─ Direct Customers
├─ Customer: City Council
│ └─ Sites: Depot East, Depot West
└─ Customer: Retail Park North
Rules of thumb - Keep billing/contract entities at Distributor/Reseller/Customer nodes. - Keep physical assets (charge points) at Site (Location) nodes. - Prefer many Sites over nested sub‑sites for clearer SLA ownership.
2) Seeded Roles & Scope Bundles
Map roles to scopes (examples assume chargepoint/tenant scopes). Adjust to your needs.
Platform (outside tenant)
- PLATFORM_ADMIN (via api‑auth) → cross‑tenant administration with
?as_tenant=
Tenant‑level defaults
- TENANT_ADMIN
- Full read/write across tenant entities.
-
Scopes:
TENANT_READ TENANT_WRITE DOMAIN_* ORG_* LOC_* USER_* ROLE_WRITE PERM_WRITE MEMBERSHIP_* APPROVAL_* AUDIT_READ- Chargepoint read/write:
CP_DEVICE_* CP_CONNECTOR_* CP_LOGS_* CP_ADMIN_SETTINGS
- Chargepoint read/write:
-
TENANT_MANAGER
- Operates org structure, locations, and users; not roles/permissions.
-
Scopes:
ORG_READ ORG_WRITE LOC_READ LOC_WRITE USER_READ USER_WRITE MEMBERSHIP_READ MEMBERSHIP_WRITE -
TENANT_VIEWER
- Read‑only across tenant domain.
- Scopes:
TENANT_READ ORG_READ LOC_READ USER_READ AUDIT_READ
Channel roles (for hierarchy nodes)
- DISTRIBUTOR_ADMIN
- Manage child Resellers and Customers under a Distributor org.
-
Scopes:
ORG_READ ORG_WRITE LOC_READ USER_READ USER_WRITE MEMBERSHIP_*(scoped to subtree via portal policy) -
RESELLER_ADMIN
- Manage Customers under a Reseller org.
- Scopes:
ORG_READ ORG_WRITE LOC_READ LOC_WRITE USER_READ USER_WRITE MEMBERSHIP_*(subtree)
Customer roles
- CUSTOMER_ADMIN
- Manage Sites, users, and memberships for the customer org.
-
Scopes:
ORG_READ LOC_* USER_* MEMBERSHIP_* CP_DEVICE_READ CP_CONNECTOR_READ CP_TXN_READ CP_LOGS_READ+CP_DEVICE_EDIT -
SITE_MANAGER
- Operate a specific Site (Location) and devices under it.
-
Scopes:
LOC_READ USER_READ CP_DEVICE_READ CP_CONNECTOR_READ CP_TXN_READ CP_LOGS_READ CP_DEVICE_EDIT CP_CONNECTOR_EDIT -
OPERATOR
- Day‑to‑day charging ops (remote start/stop/reset if enabled).
-
Scopes:
CP_DEVICE_READ CP_TXN_READ CP_OPS_REMOTE_START CP_OPS_REMOTE_STOP CP_OPS_RESET -
END_USER
- Self‑service for personal RFID/app tokens (if exposed), limited read on their sessions.
- Scopes:
USER_READand per‑user session reads (narrow).
Tip: If you manage charging profiles/schedules centrally, add
CP_ADMIN_SETTINGSto ADMIN/MANAGER roles only.
3) Recommended Seeding Flow (API)
3.1 Create tenant (platform)
3.2 Seed roles & permissions
POST /api/tenant/v1/tenants/acme/roles
{ "name": "TENANT_ADMIN" }
# repeat for TENANT_MANAGER, TENANT_VIEWER, DISTRIBUTOR_ADMIN, RESELLER_ADMIN, CUSTOMER_ADMIN, SITE_MANAGER, OPERATOR, END_USER
POST /api/tenant/v1/tenants/acme/roles/{roleId}/permissions
{ "permission_ids": ["TENANT_READ","ORG_WRITE","LOC_WRITE","CP_DEVICE_READ","CP_DEVICE_EDIT", "..."] }
3.3 Create org hierarchy
POST /api/tenant/v1/tenants/acme/orgs
{ "name": "Acme Distributors" } # distributor
POST /api/tenant/v1/tenants/acme/orgs
{ "name": "Voltify UK", "parent_id": "<distributor>" } # reseller
POST /api/tenant/v1/tenants/acme/orgs
{ "name": "GreenFleet Ltd", "parent_id": "<reseller>" } # customer
3.4 Create locations (sites)
POST /api/tenant/v1/tenants/acme/locations
{ "organisation_id": "<greenfleet>", "name": "Depot A", "post_code": "DE1 1AA", "country": "GB", "is_default": true }
3.5 Add users & memberships
POST /api/tenant/v1/tenants/acme/users
{ "name": "Jane Smith", "email": "jane@greenfleet.com" }
POST /api/tenant/v1/tenants/acme/memberships
{ "user_id": "<jane>", "role_id": "<SITE_MANAGER>" }
Apply subtree scoping in the portal/UI: a role at an org node grants access to that node and all descendants.
4) Policy Notes
- Single default Site per Customer: enforce
is_default=trueuniqueness pertenant + customer org. - Least privilege: start with VIEWER or OPERATOR and elevate per need.
- Audit everything: write an AuditLog for structure changes, role/membership changes, and domain verification.
- Cross‑tenant (platform): only with
PLATFORM_ADMINvia?as_tenant=. - PII minimization: avoid exposing personal data across org boundaries.
5) Example: Access Matrix (cheat‑sheet)
| Role | Org scope | Structure admin | Site admin | Device ops | Logs/Tx read | Profile/settings |
|---|---|---|---|---|---|---|
| TENANT_ADMIN | Tenant (all) | ✓ | ✓ | ✓ | ✓ | ✓ |
| TENANT_MANAGER | Tenant (all) | ✓ | ✓ | (opt) | ✓ | (opt) |
| TENANT_VIEWER | Tenant (all) | ✗ | ✗ | ✗ | ✓ | ✗ |
| DISTRIBUTOR_ADMIN | Distributor subtree | ✓ (subtree) | ✓ (subtree) | (opt) | ✓ (subtree) | (opt) |
| RESELLER_ADMIN | Reseller subtree | ✓ (subtree) | ✓ (subtree) | (opt) | ✓ (subtree) | (opt) |
| CUSTOMER_ADMIN | Customer subtree | limited | ✓ | (opt) | ✓ | (opt) |
| SITE_MANAGER | Site only | ✗ | ✓ | (opt) | ✓ | (opt) |
| OPERATOR | Site only | ✗ | ✗ | ✓ | ✓ | ✗ |
| END_USER | Self | ✗ | ✗ | ✗ | self only | ✗ |
6) Implementation Tips
- Persist org scope on memberships:
{ user_id, role_id, tenant_id, org_id? }to limit access to a subtree. - Add portal policy layer that resolves allowed orgs for a request and injects to service queries.
- For chargepoint views, join by
location.organisation_idto enforce org scope. - Keep role names consistent across tenants; allow custom roles via additional entries.