Endpoints

Pedidos e respostas dos endpoints da Admin API do espaço de trabalho

Esta é a referência de pedidos/respostas de todos os endpoints /v2/admin. A autenticação, idempotência, limites de pedidos, criação de tokens e semântica de âmbitos estão documentados na Admin API do espaço de trabalho. Os tipos reutilizáveis ao nível dos campos e exemplos encontram-se em Esquemas e exemplos. Leia cada linha de endpoint em conjunto com o Contrato de respostas e erros, que lista todos os corpos de insucesso comuns e específicos possíveis.

Contrato comum

Cada caminho começa pela origem app-api implementada. O espaço de trabalho deriva exclusivamente do token autenticado; os clientes não podem enviar cpoId. Todos os valores :...Id documentados nos caminhos de recursos são inteiros decimais positivos. Cada pedido exige exatamente um:

Authorization: Bearer <workspace-access-token>

Os pedidos apresentados com corpo JSON exigem Content-Type: application/json. Todas as alterações exigem Idempotency-Key (POST, PUT, PATCH e DELETE), incluindo POST /v2/admin/reports/send; salvo indicação em contrário, o tipo de conteúdo de sucesso é application/json, as datas JSON são cadeias RFC 3339 e os números são números JSON.

Cada pedido pode incluir X-Request-Id e X-Error-Format: envelope; consulte o Contrato de respostas e erros. Estes cabeçalhos não alteram os esquemas de sucesso.

Notação usada abaixo:

  • none significa sem corpo de pedido. Um DELETE que documenta um corpo JSON continua a exigir esse corpo.
  • ? assinala uma propriedade ou parâmetro de consulta opcional.
  • T | null significa que a propriedade está presente e permite null.
  • 204 empty significa sem corpo de resposta.
  • CursorPage<T> significa { items: T[], nextCursor: number|string|null }, exceto quando a linha indica explicitamente um objeto page ou pagination.
  • Todos os recursos aninhados em falta, incluindo IDs de outro espaço de trabalho, usam 404 { "error": "Workspace resource not found", "code": "RESOURCE_NOT_FOUND" }, salvo se uma função documentar um conflito mais específico.

Esquemas de pedido partilhados

Entradas de espaços de trabalho e locais

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
}

Entrada de gestão de potência

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
}

Entradas dos carregadores

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]
}

Entradas dos relatórios

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"
}

Os intervalos dos relatórios têm de cumprir from < to e não podem exceder 90 dias. Quando omitidos, são usados os últimos 90 dias. userId e anonymous=true são mutuamente exclusivos. As exportações agendadas recuperam um minuto UTC perdido e usam tentativas limitadas (espera de 5/10 minutos, concessão de reserva obsoleta de 30 minutos, três tentativas).

Entradas das tarifas

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 }

Entradas dos perfis de carregamento

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 }

Entradas de utilizadores

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 }

Pelo menos um de energyValue ou timeValue tem de ser superior a zero ao criar um limite. Os emails perdem os espaços nas extremidades e são convertidos para minúsculas. Os UIDs RFID só são aceites em escritas e são mascarados nas respostas de leitura.

Resumo dos DTOs de resposta

As tabelas de endpoints mostram todos os envelopes de resposta de nível superior. Estes são os principais DTOs reutilizáveis dentro desses envelopes:

DTOCampos
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; o detalhe acrescenta chargers normalizado e o estado do conector
GeocodeCandidateplaceId, displayName, latitude, longitude; credenciais e metadados específicos do fornecedor nunca são devolvidos
MetricsWindowfrom, to, limites/contagens de sessões e totalsCapped, quando aplicável
Chargerid, name, identifier, cpoId, site, status normalizado, isPublic, indicadores RFID/Smart/automatização, provisioned, connection, evses, connectors, createdAt, updatedAt; o detalhe acrescenta configuration expurgado, firmware e contagens
ConnectorportId, connectorId, IDs de referência do conector, maxVoltage, maxAmperage, maxElectricPower
ReportSessionid, carregador/local, IDs de porta e conector, utilizador minimizado, active, energyWh, início/fim/duração e custos líquidos/impostos/totais por moeda
ReportScheduleid, workspaceId, enabled, recipientEmail, format, sendDay/sendTime UTC, criação/atualização e metadados seguros da última entrega, incluindo o número de tentativas
Tariffid, cpoId, name, objetos de tipo/local/moeda/zona de ofertas, tax, fallbackPrice, price efetivo, components, assignmentCount
ChargingProfileid, cpoId, label, description, stackLevel, objetos de localização/finalidade/tipo/recorrência/unidade de taxa, validade, agendamento, assignmentCount, marcas temporais
Userid, membershipId, name, email, rfids mascarado, access do carregador, limitCount; o detalhe acrescenta phone, createdAt e limits. Credenciais de autorização OCPP como contractId nunca são devolvidas.
RFIDid, maskedUid, name, type, valid; o UID em bruto nunca é devolvido
AccessallChargers, chargerIds ordenado e items com metadados de autorização/carregador/local/expiração
Limitid, limitTypeId, label, energyValue, timeValue
LocalUserDTO de lista: id, customerId, name, email, expiresAt, systemManaged. As alterações de utilizadores locais usam as estruturas distintas LocalUserMutation/LocalUserUpdate em Esquemas e exemplos.

As rotas de lista/detalhe devolvem objetos de referência para permitir descobrir IDs válidos. Considere campos adicionados desconhecidos como compatíveis com versões futuras e distinga casos apenas por campos estáveis e códigos de erro documentados.

Endpoints do espaço de trabalho

Método e caminhoÂmbitoPedidoResposta de sucesso
GET /v2/adminworkspaces:readCorpo: nenhum200 { workspace: Workspace }
PATCH /v2/adminworkspaces:updateCorpo: WorkspacePatch200 { workspace: Workspace }
GET /v2/admin/dashboardworkspaces:read + reports:readCorpo/consulta: nenhum200 { window, stats { chargerCount, onlineChargerCount, siteCount, userCount }, sessions: DashboardSession[], analytics }; as sessões são limitadas a 1 000 ao longo de 90 dias

Endpoints de locais e gestão de potência

Método e caminhoÂmbitoPedidoResposta de sucesso
GET /v2/admin/sitessites:readCorpo/consulta: nenhum200 { items: Site[], metricsWindow: MetricsWindow, lookups { countries, timezones } }
GET /v2/admin/sites/geocodesites:readConsulta: street opcional (máx. 160), city, state, country (máx. 100 cada), countryCode (máx. 2); cabeçalho Accept-Language opcional; a combinação de rua/cidade/estado tem de conter pelo menos dois caracteres200 { results: GeocodeCandidate[] }; pesquisas idênticas em curso são agregadas e o trabalho do fornecedor usa uma fila sequencial limitada e uma cache limitada no servidor; pesquisas inválidas devolvem 400 INVALID_GEOCODE_QUERY, saturação da fila devolve 429 GEOCODING_BUSY com Retry-After e falhas do fornecedor devolvem 502 GEOCODING_UNAVAILABLE
POST /v2/admin/sitessites:createCorpo: SiteInput201 { site: Site }
GET /v2/admin/sites/:siteIdsites:read + reports:readCorpo/consulta: nenhum200 { site: SiteDetail, recentSessions: SiteSession[], totals, metricsWindow }
PATCH /v2/admin/sites/:siteIdsites:updateCorpo: SiteInput completo200 { site: Site }
DELETE /v2/admin/sites/:siteIdsites:deleteCorpo: nenhum204 empty; um local não vazio devolve 409 SITE_NOT_EMPTY
GET /v2/admin/sites/:siteId/power-managementsites:power-managementCorpo/consulta: nenhum200 { configuration: PowerConfiguration } com o local pertencente ao espaço de trabalho, carregadores/conectores, grupos, fontes de contadores, atribuições e estado de compatibilidade
PUT /v2/admin/sites/:siteId/power-managementsites:power-managementCorpo: PowerConfigurationInput200 { configuration: PowerConfiguration }; uma recusa OCPP, tempo esgotado ou alteração concorrente devolve 409 POWER_MANAGEMENT_CONFLICT sem aplicar a substituição

Endpoints dos carregadores

Método e caminhoÂmbitoPedidoResposta de sucesso
GET /v2/admin/chargerschargers:readCorpo/consulta: nenhum200 { items: Charger[], metricsWindow: MetricsWindow, connectorLookups { formats, types, powerTypes } }
POST /v2/admin/chargerschargers:createCorpo: ChargerCreate; o identificador é convertido para maiúsculas201 { charger: Charger }; a palavra-passe nunca é devolvida
GET /v2/admin/chargers/:chargerIdchargers:readCorpo/consulta: nenhum200 { charger: ChargerDetail }
PATCH /v2/admin/chargers/:chargerIdchargers:updateCorpo: ChargerPatch200 { charger: ChargerDetail }
DELETE /v2/admin/chargers/:chargerIdchargers:deleteCorpo: nenhum204 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 }; a prontidão não cumprida devolve 409 CHARGER_CONFLICT
GET /v2/admin/chargers/:chargerId/public-readinesschargers:readCorpo/consulta: nenhum200 { readiness: { ready, missingRequirements, connectorIds, missingTariffConnectorIds, ...commercialState } }
GET /v2/admin/chargers/:chargerId/logschargers:readConsulta: 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:readConsulta: 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:readConsulta: connector?: integer[0,255]200 image/png binário, 300 px, Content-Disposition: inline, Cache-Control: no-store
GET /v2/admin/chargers/:chargerId/composite-scheduleschargers:readConsulta: cursor?, limit?=20 (1..50), connectorId?200 { items: CompositeSchedule[], nextCursor: number or null }
GET /v2/admin/chargers/:chargerId/configurationchargers:readCorpo/consulta: nenhum200 { items: ChargerConfiguration[] }; os valores semelhantes a segredos são [REDACTED]
PATCH /v2/admin/chargers/:chargerId/configurationchargers:configurationCorpo: { key: string(1..50), value: string(0..500) }200 { status: string }, em que o estado é o resultado do carregador/OCPP
POST /v2/admin/chargers/:chargerId/configuration/reloadchargers:configurationCorpo: nenhum200 { status: string }
GET /v2/admin/chargers/:chargerId/firmwarechargers:readCorpo/consulta: nenhum200 { 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 }

Endpoints dos relatórios

Método e caminhoÂmbitoPedidoResposta de sucesso
GET /v2/admin/reports/datareports:readConsulta: 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/consulta: nenhum200 { schedule: ReportSchedule or null }
PUT /v2/admin/reports/schedulereports:schedulesCorpo: ReportScheduleInput estrito200 { schedule: ReportSchedule }
POST /v2/admin/reports/sendreports:sendCorpo: nenhum; o cabeçalho Idempotency-Key é obrigatório200 { sent: true, format: "csv" or "pdf", rowCount: number, range { from, to } }; a ausência de agendamento guardado devolve 409 REPORT_SCHEDULE_REQUIRED
GET /v2/admin/reports/measurementsreports:readConsulta: transactionLogId obrigatório, cursor? opaco, limit?=500 (1..1000)200 { transactionLogId, measurements: { name, unit, lines[] }[], page { limit, nextCursor, sampleValuesTruncated, sampleLimitPerMeterValue: 64 } }
GET /v2/admin/reports/exportreports:exportConsulta: ReportFilters, format?="csv" or "pdf"200 binário CSV/PDF com nome de ficheiro anexo, Cache-Control: private, no-store, X-Content-Type-Options: nosniff; mais de 2 000 linhas devolve 422 REPORT_EXPORT_ROW_LIMIT

Endpoints das tarifas

Método e caminhoÂmbitoPedidoResposta de sucesso
GET /v2/admin/tariffstariffs:readCorpo/consulta: nenhum200 { items: Tariff[], lookups { currencies, biddingZones, currencyRates, componentTypes, seasons, tariffTypes } }
POST /v2/admin/tariffstariffs:createCorpo: TariffInput201 { tariff: Tariff }
GET /v2/admin/tariffs/:tariffIdtariffs:readCorpo/consulta: nenhum200 { 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: nenhum204 empty; referências a registos de carregamento devolvem 409 TARIFF_CONFLICT
GET /v2/admin/tariffs/:tariffId/market-pricestariffs:readConsulta: startsAt obrigatório, marca temporal Unix não negativa200 { items: { id, value, currencyId, startsAt, endsAt, startsAtIso, endsAtIso, intervalMinutes }[], truncated: boolean }; no máximo 10 000 linhas
GET /v2/admin/chargers/:chargerId/tariff-assignmentschargers:tariff-assignments + tariffs:readCorpo/consulta: nenhum200 { items: TariffAssignment[], tariffs: Tariff[] }
POST /v2/admin/chargers/:chargerId/tariff-assignmentschargers:tariff-assignments + tariffs:assignCorpo: TariffAssignmentCreateInput; omitir connectorId ou connectorId=0 significa todos os conectores201 { assignments: TariffAssignment[] }
PATCH /v2/admin/chargers/:chargerId/tariff-assignments/:assignmentIdchargers:tariff-assignments + tariffs:assignCorpo: TariffAssignmentUpdateInput; o conector é obrigatório e não pode ser 0200 { assignment: TariffAssignment }
DELETE /v2/admin/chargers/:chargerId/tariff-assignments/:assignmentIdchargers:tariff-assignments + tariffs:assignCorpo: nenhum204 empty

TariffAssignment contém id, chargerId, connectorId, um resumo seguro de tarifa/moeda, createdAt e updatedAt.

Endpoints dos perfis de carregamento

Método e caminhoÂmbitoPedidoResposta de sucesso
GET /v2/admin/charging-profileschargingProfiles:readCorpo/consulta: nenhum200 { 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/consulta: nenhum200 { 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: nenhum204 empty apenas depois de todos os carregadores atribuídos aceitarem a limpeza e de o estado ser revalidado; caso contrário, 409 e o perfil permanece
GET /v2/admin/chargers/:chargerId/profile-assignmentschargers:profile-assignments + chargingProfiles:readCorpo/consulta: nenhum200 { 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: nenhum204 empty após limpeza sincronizada

ProfileAssignment contém o ID da atribuição, IDs de carregador/conector e um resumo seguro do perfil. Os objetos de resultado OCPP expõem informações estáveis de pedido/estado e nunca credenciais.

Endpoints de utilizadores, RFID, acesso, limites e utilizadores locais

Método e caminhoÂmbitoPedidoResposta de sucesso
GET /v2/admin/usersusers:readConsulta: cursor? correspondente ao estado (members: numérico positivo; invitations: i2.* opaco autenticado; all: continuação numérica do fluxo de membros antigo ou a2.* composto opaco autenticado), limit?=50 (1..100), status?="all", "members", or "invitations"; cabeçalho X-Admin-User-Search opcional (máx. 100)200 { items: User[], invitations: Invitation[], chargers: ChargerSummary[], page { nextCursor, memberNextCursor, invitationNextCursor, limit } }; os convites são paginados e preenchidos usando a mesma expressão de email normalizada, para que maiúsculas/espaços antigos não ocultem linhas; os cursores estão vinculados ao espaço de trabalho e filtro de pesquisa, não contêm emails legíveis e têm de ser devolvidos inalterados; os cursores all avançam os fluxos de membros e convites independentemente
POST /v2/admin/usersusers:createCorpo: UserCreate; o cliente tem de estar registado201 { user: User }; um email desconhecido devolve o corpo 404 RESOURCE_NOT_FOUND, que não revela a existência de recursos, e deve usar convites
POST /v2/admin/users/invitationsusers:invitations + users:access; mais users:rfid se for fornecido rfidCorpo: InvitationInput200 or 201 { result }; o resultado de associação é { kind:"attached", user, accessChanged, rfidChanged }; o resultado pendente é { kind:"invited", email, name, maskedRfid, chargerIds, createdCount, emailSent, emailRetryReady, alreadyInvited }
POST /v2/admin/users/invitations/resendusers:invitationsCorpo: { email: email }200 { result: { targetedChargers, emailSent, emailRetryReady } }; é imposto um período de espera global por destinatário
DELETE /v2/admin/users/invitationsusers:invitationsCorpo: { email: email }204 empty
GET /v2/admin/users/:userIdusers:read + reports:readCorpo/consulta: nenhum200 { user: UserDetail, sessions: UserSession[], totals { count, energyWh, durationSeconds }, window, limitTypes, chargers }; no máximo 200 sessões ao longo de 90 dias
PATCH /v2/admin/users/:userIdusers:updateCorpo: { name: string(1..255) }200 { user: User }
DELETE /v2/admin/users/:userIdusers:deleteCorpo: nenhum204 empty
GET /v2/admin/users/:userId/vehiclesusers:readConsulta: cursor? numérico, limit?=50 (1..100)200 { items: Vehicle[], page { nextCursor, limit } }; o DTO do veículo inclui campos seguros de fornecedor/estado/modelo/telemetria
GET /v2/admin/users/:userId/rfidsusers:readCorpo/consulta: nenhum200 { items: RFID[] }; os UIDs são mascarados
POST /v2/admin/users/:userId/rfidsusers:rfidCorpo: { uid: string(1..36), name?: string or null }201 { rfid: RFID }; o UID em bruto não é repetido na resposta
PATCH /v2/admin/users/:userId/rfids/:rfidIdusers:rfidCorpo: { name: string or null }200 { rfid: RFID }
DELETE /v2/admin/users/:userId/rfids/:rfidIdusers:rfidCorpo: nenhum204 empty
GET /v2/admin/users/:userId/accessusers:readCorpo/consulta: nenhum200 { access: Access }
PUT /v2/admin/users/:userId/accessusers:accessCorpo: AccessInput200 { access: Access }
DELETE /v2/admin/users/:userId/access/:accessIdusers:accessCorpo: nenhum204 empty
GET /v2/admin/users/:userId/limitsusers:readCorpo/consulta: nenhum200 { items: Limit[] }
POST /v2/admin/users/:userId/limitsusers:limitsCorpo: LimitCreate201 { limit: Limit }
PATCH /v2/admin/users/:userId/limits/:limitIdusers:limitsCorpo: LimitPatch, pelo menos uma propriedade200 { limit: Limit }
DELETE /v2/admin/users/:userId/limits/:limitIdusers:limitsCorpo: nenhum204 empty
GET /v2/admin/chargers/:chargerId/local-userschargers:read + users:readCorpo/consulta: nenhum200 { charger: ChargerSummary, items: LocalUser[], invitations: { email }[], availableMembers: [] }; a API omite deliberadamente o diretório amplo de membros disponíveis do portal
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 existente: { invited:false, localUser: LocalUserMutation }; convite: { 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: nenhum204 empty

O utilizador de sistema Tagless está protegido contra estas operações. Falhas nos emails de convite devolvem um estado de entrega seguro sem revelar detalhes SMTP; os clientes podem repetir quando emailRetryReady é true.

Respostas binárias

Duas famílias de endpoints não devolvem JSON em caso de sucesso:

EndpointTipo de conteúdoCabeçalhos importantes
QR do carregadorimage/pngCache-Control: no-store, Content-Disposition: inline, X-Content-Type-Options: nosniff
Exportação de relatóriostext/csv; charset=utf-8 ou application/pdfCache-Control: private, no-store, Content-Disposition: attachment, X-Content-Type-Options: nosniff

Os erros dos endpoints binários continuam a ser JSON.