API Reference / Utenti e team

Utenti e team

L'amministrazione di chi accede e cosa può fare: utenti, ruoli (con permessi per modulo e per campo) e team (per lo scope team della visibilità).

Solo amministratori

Questi endpoint richiedono il ruolo amministratore (requireAdminRole): un utente autenticato ma non admin riceve 403. Unica eccezione: GET /api/users/assignable, disponibile a ogni utente autenticato. Il profilo del proprio account si legge e aggiorna da /auth/me. Ogni richiesta richiede l'header Authorization: Bearer <accessToken>.

01Gli oggetti

Le tre risorse di questa pagina. Le date sono stringhe ISO 8601 in UTC. L'password_hash non è mai esposto dall'API.

L'oggetto Utente
AttributoTipoDescrizione
idintegerIdentificativo univoco dell'utente.
usernamestringNome utente univoco (3–100 caratteri: lettere, numeri, . _ -).
emailstringEmail univoca.
first_namestring | nullNome.
last_namestring | nullCognome.
is_activebooleanSe false, l'utente non può autenticarsi.
rolesarrayRuoli assegnati, come { id, name }.
created_atstringData/ora di creazione (ISO 8601).
updated_atstringData/ora ultima modifica (ISO 8601).
json · l'oggetto Utente
{
  "id": 7,
  "username": "m.rossi",
  "email": "m.rossi@acme.it",
  "first_name": "Marco",
  "last_name": "Rossi",
  "is_active": true,
  "roles": [ { "id": 2, "name": "sales" } ],
  "created_at": "2026-07-07T15:24:00.000Z",
  "updated_at": "2026-07-07T15:24:00.000Z"
}
L'oggetto Ruolo
AttributoTipoDescrizione
idintegerIdentificativo univoco del ruolo.
namestringNome univoco del ruolo (max 80 caratteri).
descriptionstring | nullDescrizione.
is_systembooleanRuolo di sistema (es. admin): non eliminabile.
parent_idinteger | nullRuolo padre nella gerarchia; null se radice.
L'oggetto Team
AttributoTipoDescrizione
idintegerIdentificativo univoco del team.
namestringNome univoco del team (2–120 caratteri).
descriptionstring | nullDescrizione (max 1000 caratteri).
isActivebooleanTeam attivo.
membersarrayMembri, come { userId, username, email, firstName, lastName, fullName, roles[], joinedAt }.
createdAtstringData/ora di creazione (ISO 8601).
updatedAtstringData/ora ultima modifica (ISO 8601).

02Utenti

MetodoPathCosa fa
GET/api/usersElenco utenti (con search, limit, offset).
GET/api/users/:idUn singolo utente.
POST/api/usersCrea un utente.
PATCH/api/users/:idModifica (ruoli, stato, dati).
DELETE/api/users/:idElimina (riassegnando i record).
POST/api/users/:id/reset-passwordReimposta la password.
GET/api/users/assignableUtenti assegnabili dal chiamante: non richiede admin.
GET /api/users admin richiesto
Parametri query
ParametroTipoDescrizione
searchstringopzionaleFiltra per username, email o nome.
limitintegeropzionaleRighe per pagina.
offsetintegeropzionaleRighe da saltare.
Richiesta
curl "https://crm.tuodominio.it/api/users?search=rossi" \
  -H "Authorization: Bearer <accessToken>"
const res = await fetch("https://crm.tuodominio.it/api/users?search=rossi", {
  headers: { Authorization: `Bearer ${accessToken}` },
});
const { data } = await res.json();
Esiti
StatoCorpo
200Elenco degli utenti nell'envelope data.
403FORBIDDEN: l'utente non è amministratore.
POST /api/users admin richiesto
Parametri body
CampoTipoNote
usernamestringrichiesto3–100 caratteri: lettere, numeri, . _ -. Univoco.
emailstringrichiestoEmail valida e univoca.
passwordstringrichiestoDeve rispettare la policy password.
firstNamestringopzionaleNome.
lastNamestringopzionaleCognome.
isActivebooleanopzionaleDefault true.
roleIdsinteger[]opzionaleRuoli da assegnare. Default vuoto.
Richiesta
curl -X POST https://crm.tuodominio.it/api/users \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{ "username": "m.rossi", "email": "m.rossi@acme.it", "password": "…", "roleIds": [2] }'
const res = await fetch("https://crm.tuodominio.it/api/users", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ username: "m.rossi", email: "m.rossi@acme.it", password: "…", roleIds: [2] }),
});
const { data } = await res.json();
Risposta · 201
json
{ "success": true, "data": { "user": { "id": 7, "username": "m.rossi",  } } }
Esiti
StatoCodiceQuando
201·Utente creato; in data.user l'oggetto completo.
400BAD_REQUESTUsername/email non validi o già in uso, password fuori policy.
403FORBIDDENL'utente non è amministratore.
PATCH/api/users/:idadmin richiesto

Modifica parziale (username, email, firstName, lastName, isActive, roleIds). Per sicurezza non puoi rimuovere a te stesso il ruolo admin, disattivarti, né togliere l'ultimo amministratore attivo (403).

DELETE/api/users/:idadmin richiesto

Elimina l'utente riassegnando i record posseduti/assegnati all'admin chiamante. Non puoi eliminare il tuo account né l'ultimo admin attivo. Risponde 200 { deleted: true }.

POST/api/users/:id/reset-passwordadmin richiesto

Body { "password": "…" } (deve rispettare la policy). Risponde 200 { updated: true }.

03Assegnazione dei record (gerarchia)

La gerarchia dei ruoli governa anche a chi puoi intestare un record (owner_user_id / assigned_user_id, su ogni modulo base e custom): un utente può assegnare solo a sé stesso o a un sottoposto (utente con un ruolo discendente del suo), e può riassegnare solo record già suoi o di un sottoposto. L'admin assegna a chiunque. Un target fuori gerarchia risponde 403.

Nota

Questo vincolo è ortogonale allo scope: lo scope (own/team/all) decide quali record raggiungi, la gerarchia decide quali valori puoi mettere in owner/assigned. Avere scope all senza ruoli figli permette di modificare i record, ma non di riassegnarli.

GET /api/users/assignable bearer richiesto

Gli utenti a cui il chiamante può assegnare un record: sé stesso + i sottoposti (admin: tutti gli utenti attivi). Alimenta il picker di owner/assegnatario. Non richiede il ruolo admin.

Risposta · 200
json
{ "success": true, "data": { "items": [{ "id": "20", "name": "Luca Bianchi" }] } }

04Ruoli e permessi

I ruoli sono gerarchici: le autorizzazioni di un ruolo figlio sono un sottoinsieme di quelle del padre (materializzate e imposte nel service). A ogni ruolo si assegnano i permessi per modulo (azione + scope) e, opzionalmente, i permessi per campo. Alla cancellazione di un padre, i figli diventano radici. La stessa gerarchia governa la riassegnazione dei record.

MetodoPathCosa fa
GET/api/rolesElenco ruoli.
POST/api/rolesCrea un ruolo.
GET/api/roles/:idUn singolo ruolo.
PATCH/api/roles/:idRinomina / sposta nella gerarchia (parentId).
DELETE/api/roles/:idElimina.
GET/api/roles/:id/permissionsPermessi per modulo del ruolo.
PUT/api/roles/:id/permissionsSostituisce i permessi per modulo.
GET/api/roles/:id/field-permissionsPermessi per campo.
PUT/api/roles/:id/field-permissionsSostituisce i permessi per campo.
Nota · PUT = sostituzione completa

I permessi e i permessi-campo si scrivono con PUT: l'insieme che invii sostituisce interamente quello esistente. Leggi prima con il GET corrispondente e reinvia la matrice completa, per non perdere assegnazioni.

PUT /api/roles/:id/permissions admin richiesto

Sostituisce l'intero set di permessi per modulo del ruolo. Body: la lista completa degli id dei permessi da assegnare.

Parametri body
CampoTipoNote
permissionIdsinteger[]richiestoSet completo di permessi. Un array vuoto revoca tutto.
Richiesta
curl -X PUT https://crm.tuodominio.it/api/roles/2/permissions \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{ "permissionIds": [1, 4, 5, 9] }'
// leggi prima la matrice corrente, poi reinvia il set completo
const res = await fetch("https://crm.tuodominio.it/api/roles/2/permissions", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ permissionIds: [1, 4, 5, 9] }),
});
const { data } = await res.json();
Esiti
StatoCorpo
200La matrice aggiornata nell'envelope data.
403FORBIDDEN: non-admin, o permessi eccedenti il ceiling del padre.

PUT /field-permissions segue lo stesso schema, con body { "items": [ … ] } (set completo dei permessi per campo).

05Team

I team servono allo scope team della visibilità dei record.

MetodoPathCosa fa
GET/api/teamsElenco team (con i membri).
GET/api/teams/:idUn singolo team.
POST/api/teamsCrea un team.
PATCH/api/teams/:idModifica.
DELETE/api/teams/:idElimina.
POST /api/teams admin richiesto
Parametri body
CampoTipoNote
namestringrichiestoNome del team. 2–120 caratteri. Univoco.
descriptionstringopzionaleMax 1000 caratteri.
isActivebooleanopzionaleDefault true.
userIdsinteger[]opzionaleMembri iniziali (interi positivi, max 200). Devono essere utenti attivi esistenti.
Richiesta
curl -X POST https://crm.tuodominio.it/api/teams \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Vendite Nord", "userIds": [7, 12] }'
const res = await fetch("https://crm.tuodominio.it/api/teams", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ name: "Vendite Nord", userIds: [7, 12] }),
});
const { data } = await res.json();
Risposta · 201
json
{ "success": true, "data": { "item": { "id": 3, "name": "Vendite Nord", "isActive": true, "members": [  ] } } }
Esiti
StatoCodiceQuando
201·Team creato; in data.item l'oggetto completo con i membri.
400BAD_REQUESTUno o più userIds inesistenti o non attivi.
422VALIDATION_ERRORname fuori misura (2–120) o payload non valido.
403FORBIDDENL'utente non è amministratore.

PATCH /api/teams/:id accetta gli stessi campi (tutti opzionali, almeno uno). Inviare userIds sostituisce l'intero elenco dei membri. DELETE risponde 200 { deleted: true, item }.