Endpoint

Richieste e risposte degli endpoint dell'API di amministrazione dello spazio di lavoro

Questo è il riferimento delle richieste e delle risposte per ogni endpoint /v2/admin. Autenticazione, idempotenza, limiti di frequenza, creazione dei token e semantica degli ambiti sono documentati in API di amministrazione dello spazio di lavoro. I tipi riutilizzabili a livello di campo e gli esempi sono in Schemi ed esempi. Leggi ogni riga degli endpoint insieme a Contratto delle risposte e degli errori, che elenca tutti i possibili corpi di errore comuni e specifici degli endpoint.

Contratto comune

Ogni percorso inizia con l’origine del servizio app-api distribuito. Lo spazio di lavoro deriva esclusivamente dal token autenticato; i client non devono inviare cpoId. Tutti i valori :...Id documentati nei percorsi delle risorse sono interi decimali positivi. Ogni richiesta richiede esattamente una:

Authorization: Bearer <workspace-access-token>

Le richieste mostrate con un corpo JSON richiedono Content-Type: application/json. Tutte le operazioni di modifica richiedono Idempotency-Key (POST, PUT, PATCH e DELETE), incluso POST /v2/admin/reports/send; salvo indicazione diversa nella riga, il tipo di contenuto in caso di successo è application/json, le date JSON sono stringhe RFC 3339 e i numeri sono numeri JSON.

Ogni richiesta può includere X-Request-Id e X-Error-Format: envelope; vedi Contratto di risposte ed errori. Queste intestazioni non cambiano gli schemi di successo.

Notazione usata di seguito:

  • none significa nessun corpo della richiesta. Un DELETE che documenta un corpo JSON richiede comunque quel corpo.
  • ? indica una proprietà o un parametro di query facoltativo.
  • T | null significa che la proprietà è presente e ammette null.
  • 204 empty significa nessun corpo della risposta.
  • CursorPage<T> significa { items: T[], nextCursor: number|string|null }, salvo quando la riga nomina esplicitamente un oggetto page o pagination.
  • Tutti i casi di risorse annidate mancanti, inclusi gli ID di altri spazi di lavoro, usano 404 { "error": "Workspace resource not found", "code": "RESOURCE_NOT_FOUND" }, salvo che una funzione documenti un conflitto più specifico.

Schemi di richiesta condivisi

Dati di ingresso di spazi di lavoro e siti

WorkspacePatch = {
  name?: string(1..100), email?: email|null, website?: string|null,
  addressLine1?: string|null, addressLine2?: string|null,
  postalCode?: string|null, city?: string|null, state?: string|null,
  countryId?: positive integer
} // at least one property

SiteInput = {
  name: string(1..255), description?: string|null = null,
  addressLine1?: string|null = null, addressLine2?: string|null = null,
  postalCode?: string|null = null, city?: string|null = null, state?: string|null = null,
  countryId: positive integer, timezoneId: positive integer,
  latitude?: number[-90,90]|null = null, longitude?: number[-180,180]|null = null
}

Dati di ingresso per la gestione della potenza

PowerConfigurationInput = { pools: PowerPoolInput[0..200] }
PowerPoolInput = {
  id?: positive integer|null, name?: string(0..255),
  limit: integer[0,100000000], meterSources?: ExternalMeterInput[0..16],
  children?: (PowerPoolInput | ChargerPoolChild)[0..1000]
}
ChargerPoolChild = { chargepointId: positive integer, priority?: integer[-1000000,1000000] }
ExternalMeterInput = {
  id?: positive integer|null, sourceId?: positive integer|null,
  provider?: "perific", name?: string(0..255), externalId: string(1..80),
  measurementMode?: "available_headroom"|"external_non_ev_load_only"|"total_site_load_including_ev",
  valueMode?: "perific_relative"|"perific_direct"|"perific_meter",
  reserveA?: integer[0,100000], offlineSafeA?: integer[0,100000],
  ttlSeconds?: integer[30,86400], enabled?: boolean
}

Dati in ingresso dei caricatori

ChargerCreate = { name: string(1..40), identifier: string(1..40), password?: string(0..40) = "", siteId: positive integer }
ChargerPatch = {
  name?: string(1..40), siteId?: positive integer, invertedRfid?: boolean,
  smartMode?: integer[0,3], automationDisabled?: boolean
} // at least one
ConnectorPatch = {
  connectorFormatId: positive integer, connectorTypeId: positive integer,
  powerTypeId: positive integer, maxVoltage: integer[1,10000],
  maxAmperage: integer[1,10000], maxElectricPower: integer[1,100000000]
}

Dati di ingresso dei report

ReportFilters = {
  from?: RFC3339, to?: RFC3339, siteId?: positive integer,
  chargerId?: positive integer, userId?: positive integer,
  anonymous?: true|false|1|0, active?: true|false|1|0
}
ReportScheduleInput = {
  enabled: boolean, recipientEmail: email(max 255; may be empty only when disabled), format: "csv"|"pdf",
  sendDay: integer[1,28], sendTime: UTC "HH:mm"
}

Gli intervalli dei report devono soddisfare from < to e non possono superare 90 giorni. Se omessi, si usano gli ultimi 90 giorni. userId e anonymous=true si escludono a vicenda. Le esportazioni pianificate vengono recuperate dopo un minuto UTC saltato e usano tentativi limitati (attesa di 5/10 minuti tra i tentativi, riserva delle attività non aggiornate di 30 minuti, tre tentativi).

Dati di ingresso delle tariffe

TariffInput = {
  name: string(1..64),
  exactly one of currencyId: positive integer, currencyCode: 3 letters, currency: 3 letters,
  biddingZoneId?: positive integer|null, tax?: finite number
}
TariffComponentInput = {
  typeId: positive integer, seasonId: positive integer, priority?: finite number,
  price: finite number, validFromMonth?: finite number, validToMonth?: finite number,
  validFromWeekDay?: finite number, validToWeekDay?: finite number,
  validFromHour?: finite number, validToHour?: finite number
}
TariffAssignmentCreateInput = { tariffId: positive integer, connectorId?: nonnegative integer }
TariffAssignmentUpdateInput = { tariffId: positive integer, connectorId: positive integer }

Dati in ingresso dei profili di ricarica

ChargingProfileInput = {
  label: string(1..40), locationId: positive integer, stackLevel: nonnegative number,
  purposeId: positive integer, kindId: positive integer,
  recurrencyKindId?: positive integer|null, validFrom?: RFC3339|null,
  validTo?: RFC3339|null, startSchedule?: RFC3339|null,
  rateUnitId: positive integer, minChargingRate?: nonnegative number|null
}
ChargingProfileCreate = ChargingProfileInput & { schedule?: SchedulePeriod[0..256] }
SchedulePeriod = { startPeriod: nonnegative number, limit: nonnegative number, numberOfPhases?: positive integer|null }
ChargingScheduleInput = { rateUnitId: positive integer, periods: SchedulePeriod[1..256] }
ProfileAssignmentInput = { profileId: positive integer, connectorId?: nonnegative integer }

Dati di ingresso degli utenti

UserCreate = { email: email(max 255), name: string(1..255) }
InvitationInput = {
  email: email, name?: string|null, rfid?: string|null,
  allChargers?: boolean, chargerIds?: unique positive integer[0..500]
} // chargerIds must be non-empty unless allChargers=true
AccessInput = { allChargers?: boolean, chargerIds?: unique positive integer[0..500] }
LimitCreate = { limitTypeId: positive integer, energyValue?: nonnegative integer = 0, timeValue?: nonnegative integer = 0 }
LimitPatch = { limitTypeId?: positive integer, energyValue?: nonnegative integer, timeValue?: nonnegative integer }

Quando si crea un limite, almeno uno tra energyValue e timeValue deve essere maggiore di zero. Alle e-mail vengono rimossi gli spazi iniziali e finali e le lettere sono convertite in minuscolo. Gli UID RFID sono accettati solo in scrittura e mascherati nelle risposte di lettura.

Riepilogo dei DTO di risposta

Le tabelle degli endpoint mostrano tutte le strutture di risposta di primo livello. Questi sono i principali DTO riutilizzabili al loro interno:

DTOCampi
Workspaceid, name, email, website, address { line1, line2, postalCode, city, state, countryId, countryCode, countryName }, createdAt, updatedAt
Siteid, name, description, address, timezone { id, name }, latitude, longitude, chargerCount; il dettaglio aggiunge chargers normalizzato e lo stato del connettore
GeocodeCandidateplaceId, displayName, latitude, longitude; le credenziali e i metadati specifici del fornitore non vengono mai restituiti
MetricsWindowfrom, to, limiti/conteggi delle sessioni e totalsCapped, dove applicabile
Chargerid, name, identifier, cpoId, site, status normalizzato, isPublic, indicatori RFID/intelligente/automazione, provisioned, connection, evses, connectors, createdAt, updatedAt; il dettaglio aggiunge configuration con dati sensibili oscurati, firmware e conteggi
ConnectorportId, connectorId, ID di riferimento del connettore, maxVoltage, maxAmperage, maxElectricPower
ReportSessionid, caricatore/sito, ID della porta e del connettore, dati utente minimizzati, active, energyWh, inizio/fine/durata e costi netti/imposte/totali per valuta
ReportScheduleid, workspaceId, enabled, recipientEmail, format, sendDay/sendTime in UTC, metadati di creazione/aggiornamento e dell'ultima consegna privi di dati sensibili, incluso il numero di tentativi
Tariffid, cpoId, name, oggetti di tipo/sito/valuta/zona di mercato, tax, fallbackPrice, price effettivo, components, assignmentCount
ChargingProfileid, cpoId, label, description, stackLevel, oggetti di posizione/scopo/tipo/ricorrenza/unità del limite, validità, pianificazione, assignmentCount, timestamp
Userid, membershipId, name, email, rfids mascherato, access del caricatore, limitCount; il dettaglio aggiunge phone, createdAt e limits. Le credenziali di autorizzazione OCPP come contractId non vengono mai restituite.
RFIDid, maskedUid, name, type, valid; l'UID grezzo non viene mai restituito
AccessallChargers, chargerIds ordinato e items contenente metadati di autorizzazione/caricatore/sito/scadenza
Limitid, limitTypeId, label, energyValue, timeValue
LocalUserDTO degli elenchi: id, customerId, name, email, expiresAt, systemManaged. Le modifiche degli utenti locali usano le distinte strutture LocalUserMutation/LocalUserUpdate descritte in Schemi ed esempi.

Le route di elenco e dettaglio restituiscono gli oggetti di riferimento per consentire ai chiamanti di individuare gli ID validi. Considera compatibili con le versioni future i campi aggiunti sconosciuti e basa le diramazioni soltanto sui campi stabili e sui codici di errore documentati.

Endpoint dello spazio di lavoro

Metodo e percorsoAmbitoRichiestaRisposta di successo
GET /v2/adminworkspaces:readCorpo: nessuno200 { workspace: Workspace }
PATCH /v2/adminworkspaces:updateCorpo: WorkspacePatch200 { workspace: Workspace }
GET /v2/admin/dashboardworkspaces:read + reports:readCorpo/query: nessuno200 { window, stats { chargerCount, onlineChargerCount, siteCount, userCount }, sessions: DashboardSession[], analytics }; le sessioni sono limitate a 1.000 in 90 giorni

Endpoint dei siti e della gestione della potenza

Metodo e percorsoAmbitoRichiestaRisposta di successo
GET /v2/admin/sitessites:readCorpo/query: nessuno200 { items: Site[], metricsWindow: MetricsWindow, lookups { countries, timezones } }
GET /v2/admin/sites/geocodesites:readQuery: street facoltativo (max 160), city, state, country (max 100 ciascuno), countryCode (max 2); intestazione Accept-Language facoltativa; sono richiesti almeno due caratteri complessivi tra via, città e stato/regione200 { results: GeocodeCandidate[] }; le ricerche identiche in corso vengono accorpate e le attività del fornitore usano una coda serializzata limitata e una cache lato server limitata; le ricerche non valide restituiscono 400 INVALID_GEOCODE_QUERY, la saturazione della coda restituisce 429 GEOCODING_BUSY con Retry-After e gli errori del fornitore restituiscono 502 GEOCODING_UNAVAILABLE
POST /v2/admin/sitessites:createCorpo: SiteInput201 { site: Site }
GET /v2/admin/sites/:siteIdsites:read + reports:readCorpo/query: nessuno200 { site: SiteDetail, recentSessions: SiteSession[], totals, metricsWindow }
PATCH /v2/admin/sites/:siteIdsites:updateCorpo: SiteInput completo200 { site: Site }
DELETE /v2/admin/sites/:siteIdsites:deleteCorpo: nessuno204 empty; un sito non vuoto restituisce 409 SITE_NOT_EMPTY
GET /v2/admin/sites/:siteId/power-managementsites:power-managementCorpo/query: nessuno200 { configuration: PowerConfiguration } contenente sito di proprietà, caricatori/connettori, gruppi, fonti dei contatori, assegnazioni e stato di compatibilità
PUT /v2/admin/sites/:siteId/power-managementsites:power-managementCorpo: PowerConfigurationInput200 { configuration: PowerConfiguration }; un rifiuto OCPP, un timeout o una modifica concorrente restituisce 409 POWER_MANAGEMENT_CONFLICT senza applicare la sostituzione

Endpoint dei caricatori

Metodo e percorsoAmbitoRichiestaRisposta di successo
GET /v2/admin/chargerschargers:readCorpo/query: nessuno200 { items: Charger[], metricsWindow: MetricsWindow, connectorLookups { formats, types, powerTypes } }
POST /v2/admin/chargerschargers:createCorpo: ChargerCreate; l’identificatore viene convertito in maiuscolo201 { charger: Charger }; la password non viene mai restituita
GET /v2/admin/chargers/:chargerIdchargers:readCorpo/query: nessuno200 { charger: ChargerDetail }
PATCH /v2/admin/chargers/:chargerIdchargers:updateCorpo: ChargerPatch200 { charger: ChargerDetail }
DELETE /v2/admin/chargers/:chargerIdchargers:deleteCorpo: nessuno204 empty
PATCH /v2/admin/chargers/:chargerId/credentialschargers:credentialsCorpo: { password: string(0..40) }200 { success: true }
PATCH /v2/admin/chargers/:chargerId/connectors/:portIdchargers:connector-settingsCorpo: ConnectorPatch200 { connector: Connector }
PATCH /v2/admin/chargers/:chargerId/public-listingchargers:publicCorpo: { isPublic: boolean }200 { isPublic: boolean }; se i requisiti di idoneità non sono soddisfatti restituisce 409 CHARGER_CONFLICT
GET /v2/admin/chargers/:chargerId/public-readinesschargers:readCorpo/query: nessuno200 { readiness: { ready, missingRequirements, connectorIds, missingTariffConnectorIds, ...commercialState } }
GET /v2/admin/chargers/:chargerId/logschargers:readQuery: cursor?, limit?=50 (1..200), from?, to?, action?, direction?, levelId?200 { items: RedactedOcppLog[], nextCursor: number or null }
GET /v2/admin/chargers/:chargerId/sessionschargers:read + reports:readQuery: page?=1, limit?=25 (1..100), from?, to?, customerId?: positive integer or "unknown"200 { items: ChargerSession[], pagination { page, limit, total, totalPages }, filters { customers }, totals, totalsCapped }
GET /v2/admin/chargers/:chargerId/qrchargers:readQuery: connector?: integer[0,255]200 image/png binario, 300 px, Content-Disposition: inline, Cache-Control: no-store
GET /v2/admin/chargers/:chargerId/composite-scheduleschargers:readQuery: cursor?, limit?=20 (1..50), connectorId?200 { items: CompositeSchedule[], nextCursor: number or null }
GET /v2/admin/chargers/:chargerId/configurationchargers:readCorpo/query: nessuno200 { items: ChargerConfiguration[] }; i valori assimilabili a segreti sono [REDACTED]
PATCH /v2/admin/chargers/:chargerId/configurationchargers:configurationCorpo: { key: string(1..50), value: string(0..500) }200 { status: string } dove lo stato è il risultato del caricatore/OCPP
POST /v2/admin/chargers/:chargerId/configuration/reloadchargers:configurationCorpo: nessuno200 { status: string }
GET /v2/admin/chargers/:chargerId/firmwarechargers:readCorpo/query: nessuno200 { firmware: { status, notifiedAt, currentVersion, bootNotifiedAt } }
POST /v2/admin/chargers/:chargerId/firmwarechargers:firmwareCorpo: { url: HTTP-or-HTTPS URL, max 2048 characters }202 { status: string }
POST /v2/admin/chargers/:chargerId/commands/resetchargers:commandsCorpo: { resetType: "Soft" or "Hard" }200 { status: string }
POST /v2/admin/chargers/:chargerId/commands/availabilitychargers:commandsCorpo: { availabilityType: "Operative" or "Inoperative" }200 { status: string }
POST /v2/admin/chargers/:chargerId/commands/triggerchargers:commandsCorpo: { messageType: "BootNotification", "Heartbeat", "StatusNotification", or "MeterValues" }200 { status: string, connectorResults: { connectorId, status }[] }
POST /v2/admin/chargers/:chargerId/commands/unlockchargers:commandsCorpo: { connectorId: positive integer }200 { status: string }
POST /v2/admin/chargers/:chargerId/commands/composite-schedulechargers:commandsCorpo: { connectorId: nonnegative integer, durationSeconds?: integer[60,604800], chargingRateUnit?: "W" or "A" }200 { status: string, connectorId: number, compositeSchedule: CompositeSchedule or null }

Endpoint dei report

Metodo e percorsoAmbitoRichiestaRisposta di successo
GET /v2/admin/reports/datareports:readQuery: ReportFilters, cursor? opaco, limit?=50 (1..100)200 { items: ReportSession[], pageTotals, page { limit, nextCursor }, range { from, to, toExclusive: true } }
GET /v2/admin/reports/schedulereports:readCorpo/query: nessuno200 { schedule: ReportSchedule or null }
PUT /v2/admin/reports/schedulereports:schedulesCorpo: schema rigoroso ReportScheduleInput200 { schedule: ReportSchedule }
POST /v2/admin/reports/sendreports:sendCorpo: nessuno; l’intestazione Idempotency-Key è obbligatoria200 { sent: true, format: "csv" or "pdf", rowCount: number, range { from, to } }; in assenza di una pianificazione salvata restituisce 409 REPORT_SCHEDULE_REQUIRED
GET /v2/admin/reports/measurementsreports:readQuery: transactionLogId obbligatorio, cursor? opaco, limit?=500 (1..1000)200 { transactionLogId, measurements: { name, unit, lines[] }[], page { limit, nextCursor, sampleValuesTruncated, sampleLimitPerMeterValue: 64 } }
GET /v2/admin/reports/exportreports:exportQuery: ReportFilters, format?="csv" or "pdf"200 binario CSV/PDF con nome file dell'allegato, Cache-Control: private, no-store, X-Content-Type-Options: nosniff; più di 2.000 righe restituiscono 422 REPORT_EXPORT_ROW_LIMIT

Endpoint delle tariffe

Metodo e percorsoAmbitoRichiestaRisposta di successo
GET /v2/admin/tariffstariffs:readCorpo/query: nessuno200 { items: Tariff[], lookups { currencies, biddingZones, currencyRates, componentTypes, seasons, tariffTypes } }
POST /v2/admin/tariffstariffs:createCorpo: TariffInput201 { tariff: Tariff }
GET /v2/admin/tariffs/:tariffIdtariffs:readCorpo/query: nessuno200 { tariff: Tariff, lookups }
PATCH /v2/admin/tariffs/:tariffIdtariffs:updateCorpo: TariffInput completo200 { tariff: Tariff }
PUT /v2/admin/tariffs/:tariffId/componentstariffs:componentsCorpo: { components: TariffComponentInput[0..256] }200 { tariff: Tariff, components: TariffComponent[] }
DELETE /v2/admin/tariffs/:tariffIdtariffs:deleteCorpo: nessuno204 empty; i riferimenti nelle registrazioni di ricarica restituiscono 409 TARIFF_CONFLICT
GET /v2/admin/tariffs/:tariffId/market-pricestariffs:readQuery: startsAt obbligatorio, timestamp Unix non negativo200 { items: { id, value, currencyId, startsAt, endsAt, startsAtIso, endsAtIso, intervalMinutes }[], truncated: boolean }; al massimo 10.000 righe
GET /v2/admin/chargers/:chargerId/tariff-assignmentschargers:tariff-assignments + tariffs:readCorpo/query: nessuno200 { items: TariffAssignment[], tariffs: Tariff[] }
POST /v2/admin/chargers/:chargerId/tariff-assignmentschargers:tariff-assignments + tariffs:assignCorpo: TariffAssignmentCreateInput; omettere connectorId o connectorId=0 significa tutti i connettori201 { assignments: TariffAssignment[] }
PATCH /v2/admin/chargers/:chargerId/tariff-assignments/:assignmentIdchargers:tariff-assignments + tariffs:assignCorpo: TariffAssignmentUpdateInput; il connettore è obbligatorio e non può essere 0200 { assignment: TariffAssignment }
DELETE /v2/admin/chargers/:chargerId/tariff-assignments/:assignmentIdchargers:tariff-assignments + tariffs:assignCorpo: nessuno204 empty

TariffAssignment contiene id, chargerId, connectorId, un riepilogo non sensibile di tariffa/valuta, createdAt e updatedAt.

Endpoint dei profili di ricarica

Metodo e percorsoAmbitoRichiestaRisposta di successo
GET /v2/admin/charging-profileschargingProfiles:readCorpo/query: nessuno200 { items: ChargingProfile[], lookups { locations, purposes, kinds, recurrencyKinds, rateUnits, allowedRateUnits } }
POST /v2/admin/charging-profileschargingProfiles:createCorpo: ChargingProfileCreate201 { profile: ChargingProfile }
GET /v2/admin/charging-profiles/:profileIdchargingProfiles:readCorpo/query: nessuno200 { profile: ChargingProfile, lookups, isEditBlocked: boolean, usage { simpleChargingSchedule, smartSchedule, chargePoolChargepoint, isUsed } }
PATCH /v2/admin/charging-profiles/:profileIdchargingProfiles:updateCorpo: ChargingProfileInput completo200 { profile: ChargingProfile, ocppSync: OcppCommandResult[] }
PUT /v2/admin/charging-profiles/:profileId/schedulechargingProfiles:updateCorpo: ChargingScheduleInput200 { profile: ChargingProfile, schedule: SchedulePeriod[], ocppSync: OcppCommandResult[] }
DELETE /v2/admin/charging-profiles/:profileIdchargingProfiles:deleteCorpo: nessuno204 empty solo dopo che tutti i caricatori assegnati hanno accettato la cancellazione e lo stato è stato riconvalidato; altrimenti 409 e il profilo viene mantenuto
GET /v2/admin/chargers/:chargerId/profile-assignmentschargers:profile-assignments + chargingProfiles:readCorpo/query: nessuno200 { items: ProfileAssignment[], profiles: ChargingProfile[] }
POST /v2/admin/chargers/:chargerId/profile-assignmentschargers:profile-assignments + chargingProfiles:assignCorpo: ProfileAssignmentInput201 { assignment: ProfileAssignment, ocpp: OcppCommandResult }
PATCH /v2/admin/chargers/:chargerId/profile-assignments/:assignmentIdchargers:profile-assignments + chargingProfiles:assignCorpo: ProfileAssignmentInput200 { assignment: ProfileAssignment, ocpp: OcppCommandResult }
DELETE /v2/admin/chargers/:chargerId/profile-assignments/:assignmentIdchargers:profile-assignments + chargingProfiles:assignCorpo: nessuno204 empty dopo la cancellazione sincronizzata

ProfileAssignment contiene l'ID dell'assegnazione, gli ID del caricatore/connettore e un riepilogo non sensibile del profilo. Gli oggetti dei risultati OCPP espongono informazioni stabili su richiesta/stato e non contengono mai credenziali.

Endpoint di utenti, RFID, accessi, limiti e utenti locali

Metodo e percorsoAmbitoRichiestaRisposta di successo
GET /v2/admin/usersusers:readQuery: cursor? corrispondente allo stato (members: numerico positivo; invitations: i2.* opaco autenticato; all: continuazione numerica dei membri nel formato precedente oppure a2.* composito opaco autenticato), limit?=50 (1..100), status?="all", "members", or "invitations"; intestazione X-Admin-User-Search facoltativa (max 100)200 { items: User[], invitations: Invitation[], chargers: ChargerSummary[], page { nextCursor, memberNextCursor, invitationNextCursor, limit } }; gli inviti vengono paginati e completati usando la stessa espressione email normalizzata, così maiuscole/minuscole o spazi dei dati precedenti non possono nascondere righe; i cursori sono vincolati allo spazio di lavoro e al filtro di ricerca, non contengono indirizzi email leggibili e devono essere reinviati senza modifiche; i cursori all fanno avanzare in modo indipendente i flussi di membri e inviti
POST /v2/admin/usersusers:createCorpo: UserCreate; il cliente deve essere già registrato201 { user: User }; un'email sconosciuta restituisce il corpo 404 RESOURCE_NOT_FOUND, che non consente l'enumerazione, e deve essere gestita tramite gli inviti
POST /v2/admin/users/invitationsusers:invitations + users:access; più users:rfid se viene fornito rfidCorpo: InvitationInput200 or 201 { result }; il risultato di associazione è { kind:"attached", user, accessChanged, rfidChanged }; il risultato in attesa è { kind:"invited", email, name, maskedRfid, chargerIds, createdCount, emailSent, emailRetryReady, alreadyInvited }
POST /v2/admin/users/invitations/resendusers:invitationsCorpo: { email: email }200 { result: { targetedChargers, emailSent, emailRetryReady } }; viene applicato un intervallo minimo tra le consegne globale per destinatario
DELETE /v2/admin/users/invitationsusers:invitationsCorpo: { email: email }204 empty
GET /v2/admin/users/:userIdusers:read + reports:readCorpo/query: nessuno200 { user: UserDetail, sessions: UserSession[], totals { count, energyWh, durationSeconds }, window, limitTypes, chargers }; al massimo 200 sessioni in 90 giorni
PATCH /v2/admin/users/:userIdusers:updateCorpo: { name: string(1..255) }200 { user: User }
DELETE /v2/admin/users/:userIdusers:deleteCorpo: nessuno204 empty
GET /v2/admin/users/:userId/vehiclesusers:readQuery: cursor? numerico, limit?=50 (1..100)200 { items: Vehicle[], page { nextCursor, limit } }; il DTO del veicolo include campi non sensibili di fornitore/stato/modello/telemetria
GET /v2/admin/users/:userId/rfidsusers:readCorpo/query: nessuno200 { items: RFID[] }; gli UID sono mascherati
POST /v2/admin/users/:userId/rfidsusers:rfidCorpo: { uid: string(1..36), name?: string or null }201 { rfid: RFID }; l'UID grezzo non viene riportato nella risposta
PATCH /v2/admin/users/:userId/rfids/:rfidIdusers:rfidCorpo: { name: string or null }200 { rfid: RFID }
DELETE /v2/admin/users/:userId/rfids/:rfidIdusers:rfidCorpo: nessuno204 empty
GET /v2/admin/users/:userId/accessusers:readCorpo/query: nessuno200 { access: Access }
PUT /v2/admin/users/:userId/accessusers:accessCorpo: AccessInput200 { access: Access }
DELETE /v2/admin/users/:userId/access/:accessIdusers:accessCorpo: nessuno204 empty
GET /v2/admin/users/:userId/limitsusers:readCorpo/query: nessuno200 { items: Limit[] }
POST /v2/admin/users/:userId/limitsusers:limitsCorpo: LimitCreate201 { limit: Limit }
PATCH /v2/admin/users/:userId/limits/:limitIdusers:limitsCorpo: LimitPatch, almeno una proprietà200 { limit: Limit }
DELETE /v2/admin/users/:userId/limits/:limitIdusers:limitsCorpo: nessuno204 empty
GET /v2/admin/chargers/:chargerId/local-userschargers:read + users:readCorpo/query: nessuno200 { charger: ChargerSummary, items: LocalUser[], invitations: { email }[], availableMembers: [] }; l'API omette intenzionalmente l'ampia directory dei membri disponibili nel portale
POST /v2/admin/chargers/:chargerId/local-userschargers:local-users + users:accessCorpo: { customerId: positive integer, expiresAt?: RFC3339 or null }201 { localUser: LocalUserMutation }
POST /v2/admin/chargers/:chargerId/local-users/invitationschargers:local-users + users:invitations + users:accessCorpo: { email: email }200 or 201 { result }; cliente esistente: { invited:false, localUser: LocalUserMutation }; invito: { invited:true, alreadyInvited, emailSent, emailRetryReady }
DELETE /v2/admin/chargers/:chargerId/local-users/invitationschargers:local-users + users:invitationsCorpo: { email: email }204 empty
PATCH /v2/admin/chargers/:chargerId/local-users/:localUserIdchargers:local-users + users:accessCorpo: { expiresAt: RFC3339 or null }200 { localUser: LocalUserUpdate }
DELETE /v2/admin/chargers/:chargerId/local-users/:localUserIdchargers:local-users + users:accessCorpo: nessuno204 empty

L'utente di sistema Tagless è protetto da queste operazioni. Gli errori delle email di invito restituiscono uno stato di consegna non sensibile senza rivelare i dettagli SMTP; i chiamanti possono riprovare quando emailRetryReady è true.

Risposte binarie

Due famiglie di endpoint non restituiscono JSON in caso di successo:

EndpointTipo di contenutoIntestazioni importanti
QR del caricatoreimage/pngCache-Control: no-store, Content-Disposition: inline, X-Content-Type-Options: nosniff
Esportazione dei reporttext/csv; charset=utf-8 o application/pdfCache-Control: private, no-store, Content-Disposition: attachment, X-Content-Type-Options: nosniff

Gli errori degli endpoint binari rimangono in JSON.