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:
nonesignifica sem corpo de pedido. UmDELETEque documenta um corpo JSON continua a exigir esse corpo.?assinala uma propriedade ou parâmetro de consulta opcional.T | nullsignifica que a propriedade está presente e permite null.204 emptysignifica sem corpo de resposta.CursorPage<T>significa{ items: T[], nextCursor: number|string|null }, exceto quando a linha indica explicitamente um objetopageoupagination.- 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:
| DTO | Campos |
|---|---|
Workspace | id, name, email, website, address { line1, line2, postalCode, city, state, countryId, countryCode, countryName }, createdAt, updatedAt |
Site | id, name, description, address, timezone { id, name }, latitude, longitude, chargerCount; o detalhe acrescenta chargers normalizado e o estado do conector |
GeocodeCandidate | placeId, displayName, latitude, longitude; credenciais e metadados específicos do fornecedor nunca são devolvidos |
MetricsWindow | from, to, limites/contagens de sessões e totalsCapped, quando aplicável |
Charger | id, 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 |
Connector | portId, connectorId, IDs de referência do conector, maxVoltage, maxAmperage, maxElectricPower |
ReportSession | id, carregador/local, IDs de porta e conector, utilizador minimizado, active, energyWh, início/fim/duração e custos líquidos/impostos/totais por moeda |
ReportSchedule | id, workspaceId, enabled, recipientEmail, format, sendDay/sendTime UTC, criação/atualização e metadados seguros da última entrega, incluindo o número de tentativas |
Tariff | id, cpoId, name, objetos de tipo/local/moeda/zona de ofertas, tax, fallbackPrice, price efetivo, components, assignmentCount |
ChargingProfile | id, cpoId, label, description, stackLevel, objetos de localização/finalidade/tipo/recorrência/unidade de taxa, validade, agendamento, assignmentCount, marcas temporais |
User | id, 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. |
RFID | id, maskedUid, name, type, valid; o UID em bruto nunca é devolvido |
Access | allChargers, chargerIds ordenado e items com metadados de autorização/carregador/local/expiração |
Limit | id, limitTypeId, label, energyValue, timeValue |
LocalUser | DTO 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 | Âmbito | Pedido | Resposta de sucesso |
|---|---|---|---|
GET /v2/admin | workspaces:read | Corpo: nenhum | 200 { workspace: Workspace } |
PATCH /v2/admin | workspaces:update | Corpo: WorkspacePatch | 200 { workspace: Workspace } |
GET /v2/admin/dashboard | workspaces:read + reports:read | Corpo/consulta: nenhum | 200 { 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 | Âmbito | Pedido | Resposta de sucesso |
|---|---|---|---|
GET /v2/admin/sites | sites:read | Corpo/consulta: nenhum | 200 { items: Site[], metricsWindow: MetricsWindow, lookups { countries, timezones } } |
GET /v2/admin/sites/geocode | sites:read | Consulta: 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 caracteres | 200 { 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/sites | sites:create | Corpo: SiteInput | 201 { site: Site } |
GET /v2/admin/sites/:siteId | sites:read + reports:read | Corpo/consulta: nenhum | 200 { site: SiteDetail, recentSessions: SiteSession[], totals, metricsWindow } |
PATCH /v2/admin/sites/:siteId | sites:update | Corpo: SiteInput completo | 200 { site: Site } |
DELETE /v2/admin/sites/:siteId | sites:delete | Corpo: nenhum | 204 empty; um local não vazio devolve 409 SITE_NOT_EMPTY |
GET /v2/admin/sites/:siteId/power-management | sites:power-management | Corpo/consulta: nenhum | 200 { 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-management | sites:power-management | Corpo: PowerConfigurationInput | 200 { 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 | Âmbito | Pedido | Resposta de sucesso |
|---|---|---|---|
GET /v2/admin/chargers | chargers:read | Corpo/consulta: nenhum | 200 { items: Charger[], metricsWindow: MetricsWindow, connectorLookups { formats, types, powerTypes } } |
POST /v2/admin/chargers | chargers:create | Corpo: ChargerCreate; o identificador é convertido para maiúsculas | 201 { charger: Charger }; a palavra-passe nunca é devolvida |
GET /v2/admin/chargers/:chargerId | chargers:read | Corpo/consulta: nenhum | 200 { charger: ChargerDetail } |
PATCH /v2/admin/chargers/:chargerId | chargers:update | Corpo: ChargerPatch | 200 { charger: ChargerDetail } |
DELETE /v2/admin/chargers/:chargerId | chargers:delete | Corpo: nenhum | 204 empty |
PATCH /v2/admin/chargers/:chargerId/credentials | chargers:credentials | Corpo: { password: string(0..40) } | 200 { success: true } |
PATCH /v2/admin/chargers/:chargerId/connectors/:portId | chargers:connector-settings | Corpo: ConnectorPatch | 200 { connector: Connector } |
PATCH /v2/admin/chargers/:chargerId/public-listing | chargers:public | Corpo: { isPublic: boolean } | 200 { isPublic: boolean }; a prontidão não cumprida devolve 409 CHARGER_CONFLICT |
GET /v2/admin/chargers/:chargerId/public-readiness | chargers:read | Corpo/consulta: nenhum | 200 { readiness: { ready, missingRequirements, connectorIds, missingTariffConnectorIds, ...commercialState } } |
GET /v2/admin/chargers/:chargerId/logs | chargers:read | Consulta: cursor?, limit?=50 (1..200), from?, to?, action?, direction?, levelId? | 200 { items: RedactedOcppLog[], nextCursor: number or null } |
GET /v2/admin/chargers/:chargerId/sessions | chargers:read + reports:read | Consulta: 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/qr | chargers:read | Consulta: connector?: integer[0,255] | 200 image/png binário, 300 px, Content-Disposition: inline, Cache-Control: no-store |
GET /v2/admin/chargers/:chargerId/composite-schedules | chargers:read | Consulta: cursor?, limit?=20 (1..50), connectorId? | 200 { items: CompositeSchedule[], nextCursor: number or null } |
GET /v2/admin/chargers/:chargerId/configuration | chargers:read | Corpo/consulta: nenhum | 200 { items: ChargerConfiguration[] }; os valores semelhantes a segredos são [REDACTED] |
PATCH /v2/admin/chargers/:chargerId/configuration | chargers:configuration | Corpo: { 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/reload | chargers:configuration | Corpo: nenhum | 200 { status: string } |
GET /v2/admin/chargers/:chargerId/firmware | chargers:read | Corpo/consulta: nenhum | 200 { firmware: { status, notifiedAt, currentVersion, bootNotifiedAt } } |
POST /v2/admin/chargers/:chargerId/firmware | chargers:firmware | Corpo: { url: HTTP-or-HTTPS URL, max 2048 characters } | 202 { status: string } |
POST /v2/admin/chargers/:chargerId/commands/reset | chargers:commands | Corpo: { resetType: "Soft" or "Hard" } | 200 { status: string } |
POST /v2/admin/chargers/:chargerId/commands/availability | chargers:commands | Corpo: { availabilityType: "Operative" or "Inoperative" } | 200 { status: string } |
POST /v2/admin/chargers/:chargerId/commands/trigger | chargers:commands | Corpo: { messageType: "BootNotification", "Heartbeat", "StatusNotification", or "MeterValues" } | 200 { status: string, connectorResults: { connectorId, status }[] } |
POST /v2/admin/chargers/:chargerId/commands/unlock | chargers:commands | Corpo: { connectorId: positive integer } | 200 { status: string } |
POST /v2/admin/chargers/:chargerId/commands/composite-schedule | chargers:commands | Corpo: { 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 | Âmbito | Pedido | Resposta de sucesso |
|---|---|---|---|
GET /v2/admin/reports/data | reports:read | Consulta: ReportFilters, cursor? opaco, limit?=50 (1..100) | 200 { items: ReportSession[], pageTotals, page { limit, nextCursor }, range { from, to, toExclusive: true } } |
GET /v2/admin/reports/schedule | reports:read | Corpo/consulta: nenhum | 200 { schedule: ReportSchedule or null } |
PUT /v2/admin/reports/schedule | reports:schedules | Corpo: ReportScheduleInput estrito | 200 { schedule: ReportSchedule } |
POST /v2/admin/reports/send | reports:send | Corpo: nenhum; o cabeçalho Idempotency-Key é obrigatório | 200 { 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/measurements | reports:read | Consulta: 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/export | reports:export | Consulta: 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 | Âmbito | Pedido | Resposta de sucesso |
|---|---|---|---|
GET /v2/admin/tariffs | tariffs:read | Corpo/consulta: nenhum | 200 { items: Tariff[], lookups { currencies, biddingZones, currencyRates, componentTypes, seasons, tariffTypes } } |
POST /v2/admin/tariffs | tariffs:create | Corpo: TariffInput | 201 { tariff: Tariff } |
GET /v2/admin/tariffs/:tariffId | tariffs:read | Corpo/consulta: nenhum | 200 { tariff: Tariff, lookups } |
PATCH /v2/admin/tariffs/:tariffId | tariffs:update | Corpo: TariffInput completo | 200 { tariff: Tariff } |
PUT /v2/admin/tariffs/:tariffId/components | tariffs:components | Corpo: { components: TariffComponentInput[0..256] } | 200 { tariff: Tariff, components: TariffComponent[] } |
DELETE /v2/admin/tariffs/:tariffId | tariffs:delete | Corpo: nenhum | 204 empty; referências a registos de carregamento devolvem 409 TARIFF_CONFLICT |
GET /v2/admin/tariffs/:tariffId/market-prices | tariffs:read | Consulta: startsAt obrigatório, marca temporal Unix não negativa | 200 { items: { id, value, currencyId, startsAt, endsAt, startsAtIso, endsAtIso, intervalMinutes }[], truncated: boolean }; no máximo 10 000 linhas |
GET /v2/admin/chargers/:chargerId/tariff-assignments | chargers:tariff-assignments + tariffs:read | Corpo/consulta: nenhum | 200 { items: TariffAssignment[], tariffs: Tariff[] } |
POST /v2/admin/chargers/:chargerId/tariff-assignments | chargers:tariff-assignments + tariffs:assign | Corpo: TariffAssignmentCreateInput; omitir connectorId ou connectorId=0 significa todos os conectores | 201 { assignments: TariffAssignment[] } |
PATCH /v2/admin/chargers/:chargerId/tariff-assignments/:assignmentId | chargers:tariff-assignments + tariffs:assign | Corpo: TariffAssignmentUpdateInput; o conector é obrigatório e não pode ser 0 | 200 { assignment: TariffAssignment } |
DELETE /v2/admin/chargers/:chargerId/tariff-assignments/:assignmentId | chargers:tariff-assignments + tariffs:assign | Corpo: nenhum | 204 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 | Âmbito | Pedido | Resposta de sucesso |
|---|---|---|---|
GET /v2/admin/charging-profiles | chargingProfiles:read | Corpo/consulta: nenhum | 200 { items: ChargingProfile[], lookups { locations, purposes, kinds, recurrencyKinds, rateUnits, allowedRateUnits } } |
POST /v2/admin/charging-profiles | chargingProfiles:create | Corpo: ChargingProfileCreate | 201 { profile: ChargingProfile } |
GET /v2/admin/charging-profiles/:profileId | chargingProfiles:read | Corpo/consulta: nenhum | 200 { profile: ChargingProfile, lookups, isEditBlocked: boolean, usage { simpleChargingSchedule, smartSchedule, chargePoolChargepoint, isUsed } } |
PATCH /v2/admin/charging-profiles/:profileId | chargingProfiles:update | Corpo: ChargingProfileInput completo | 200 { profile: ChargingProfile, ocppSync: OcppCommandResult[] } |
PUT /v2/admin/charging-profiles/:profileId/schedule | chargingProfiles:update | Corpo: ChargingScheduleInput | 200 { profile: ChargingProfile, schedule: SchedulePeriod[], ocppSync: OcppCommandResult[] } |
DELETE /v2/admin/charging-profiles/:profileId | chargingProfiles:delete | Corpo: nenhum | 204 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-assignments | chargers:profile-assignments + chargingProfiles:read | Corpo/consulta: nenhum | 200 { items: ProfileAssignment[], profiles: ChargingProfile[] } |
POST /v2/admin/chargers/:chargerId/profile-assignments | chargers:profile-assignments + chargingProfiles:assign | Corpo: ProfileAssignmentInput | 201 { assignment: ProfileAssignment, ocpp: OcppCommandResult } |
PATCH /v2/admin/chargers/:chargerId/profile-assignments/:assignmentId | chargers:profile-assignments + chargingProfiles:assign | Corpo: ProfileAssignmentInput | 200 { assignment: ProfileAssignment, ocpp: OcppCommandResult } |
DELETE /v2/admin/chargers/:chargerId/profile-assignments/:assignmentId | chargers:profile-assignments + chargingProfiles:assign | Corpo: nenhum | 204 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 | Âmbito | Pedido | Resposta de sucesso |
|---|---|---|---|
GET /v2/admin/users | users:read | Consulta: 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/users | users:create | Corpo: UserCreate; o cliente tem de estar registado | 201 { 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/invitations | users:invitations + users:access; mais users:rfid se for fornecido rfid | Corpo: InvitationInput | 200 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/resend | users:invitations | Corpo: { email: email } | 200 { result: { targetedChargers, emailSent, emailRetryReady } }; é imposto um período de espera global por destinatário |
DELETE /v2/admin/users/invitations | users:invitations | Corpo: { email: email } | 204 empty |
GET /v2/admin/users/:userId | users:read + reports:read | Corpo/consulta: nenhum | 200 { 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/:userId | users:update | Corpo: { name: string(1..255) } | 200 { user: User } |
DELETE /v2/admin/users/:userId | users:delete | Corpo: nenhum | 204 empty |
GET /v2/admin/users/:userId/vehicles | users:read | Consulta: 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/rfids | users:read | Corpo/consulta: nenhum | 200 { items: RFID[] }; os UIDs são mascarados |
POST /v2/admin/users/:userId/rfids | users:rfid | Corpo: { 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/:rfidId | users:rfid | Corpo: { name: string or null } | 200 { rfid: RFID } |
DELETE /v2/admin/users/:userId/rfids/:rfidId | users:rfid | Corpo: nenhum | 204 empty |
GET /v2/admin/users/:userId/access | users:read | Corpo/consulta: nenhum | 200 { access: Access } |
PUT /v2/admin/users/:userId/access | users:access | Corpo: AccessInput | 200 { access: Access } |
DELETE /v2/admin/users/:userId/access/:accessId | users:access | Corpo: nenhum | 204 empty |
GET /v2/admin/users/:userId/limits | users:read | Corpo/consulta: nenhum | 200 { items: Limit[] } |
POST /v2/admin/users/:userId/limits | users:limits | Corpo: LimitCreate | 201 { limit: Limit } |
PATCH /v2/admin/users/:userId/limits/:limitId | users:limits | Corpo: LimitPatch, pelo menos uma propriedade | 200 { limit: Limit } |
DELETE /v2/admin/users/:userId/limits/:limitId | users:limits | Corpo: nenhum | 204 empty |
GET /v2/admin/chargers/:chargerId/local-users | chargers:read + users:read | Corpo/consulta: nenhum | 200 { 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-users | chargers:local-users + users:access | Corpo: { customerId: positive integer, expiresAt?: RFC3339 or null } | 201 { localUser: LocalUserMutation } |
POST /v2/admin/chargers/:chargerId/local-users/invitations | chargers:local-users + users:invitations + users:access | Corpo: { 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/invitations | chargers:local-users + users:invitations | Corpo: { email: email } | 204 empty |
PATCH /v2/admin/chargers/:chargerId/local-users/:localUserId | chargers:local-users + users:access | Corpo: { expiresAt: RFC3339 or null } | 200 { localUser: LocalUserUpdate } |
DELETE /v2/admin/chargers/:chargerId/local-users/:localUserId | chargers:local-users + users:access | Corpo: nenhum | 204 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:
| Endpoint | Tipo de conteúdo | Cabeçalhos importantes |
|---|---|---|
| QR do carregador | image/png | Cache-Control: no-store, Content-Disposition: inline, X-Content-Type-Options: nosniff |
| Exportação de relatórios | text/csv; charset=utf-8 ou application/pdf | Cache-Control: private, no-store, Content-Disposition: attachment, X-Content-Type-Options: nosniff |
Os erros dos endpoints binários continuam a ser JSON.