openapi: 3.0.3 info: title: Entrosity Edge Portal API version: 0.1.0 description: | Entrosity Edge portal REST API: physical access control with TRAcK ACCESS controllers, RFID readers and cards. This file is the source of truth for the backend server interfaces (oapi-codegen), request validation, and the frontend types (openapi-typescript). Run `make gen` after editing. Authentication: `Authorization: Bearer `: users sign in on Entrosity Hub, which issues five-minute product tokens for Edge (`POST /api/platform/v1/auth/product-token {"product":"edge"}`, with the Hub's session cookie). Users, tenants (the Hub's organizations) and roles are managed on the Hub; Edge keeps a copy. Every route's access rule (public, authenticated, global admin, tenant permission) is declared in `internal/http/portal/access.go` and enforced before the handler runs. servers: - url: /api/v1 tags: - name: system - name: auth - name: admin - name: tenant - name: connectors - name: controllers - name: doors - name: people - name: policy - name: events security: - bearerAuth: [] paths: /healthz: get: operationId: getHealthz summary: Liveness probe tags: - system security: [] responses: '200': description: The API process is up. content: application/json: schema: $ref: '#/components/schemas/Health' default: $ref: '#/components/responses/Problem' /auth/sse-token: post: operationId: createStreamToken summary: Short-lived token for a tenant live stream (EventSource) description: | Returns a token valid for 60 seconds that opens `GET /tenants/{tenantID}/stream?sse_token=` for the given tenant and the caller's session. Access to the tenant is checked when the stream opens. Keeps the access token out of URLs. tags: - auth requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - tenant_id properties: tenant_id: type: string format: uuid responses: '200': description: Stream token. content: application/json: schema: type: object required: - token - expires_in properties: token: type: string expires_in: type: integer description: Seconds. default: $ref: '#/components/responses/Problem' /me: get: operationId: getMe summary: The signed-in user with their tenants and roles tags: - auth responses: '200': description: Current user. content: application/json: schema: $ref: '#/components/schemas/Me' default: $ref: '#/components/responses/Problem' /admin/overview: get: operationId: getAdminOverview summary: Cross-tenant counters tags: - admin responses: '200': description: Overview. content: application/json: schema: $ref: '#/components/schemas/AdminOverview' default: $ref: '#/components/responses/Problem' /admin/tenants: get: operationId: listTenants summary: List tenants tags: - admin parameters: - $ref: '#/components/parameters/Page' - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/Query' - name: status in: query schema: $ref: '#/components/schemas/TenantStatus' responses: '200': description: Tenants. content: application/json: schema: $ref: '#/components/schemas/TenantList' default: $ref: '#/components/responses/Problem' /admin/users: get: operationId: listGlobalAdmins summary: Global admins (the platform admins of Entrosity Hub) tags: - admin responses: '200': description: Users. content: application/json: schema: $ref: '#/components/schemas/UserDirectory' default: $ref: '#/components/responses/Problem' /admin/audit: get: operationId: listAdminAudit summary: Cross-tenant audit log tags: - admin parameters: - $ref: '#/components/parameters/Page' - $ref: '#/components/parameters/PageSize' - name: tenant_id in: query schema: type: string format: uuid - $ref: '#/components/parameters/AuditActor' - $ref: '#/components/parameters/AuditAction' - $ref: '#/components/parameters/AuditResourceType' - $ref: '#/components/parameters/AuditFrom' - $ref: '#/components/parameters/AuditTo' responses: '200': description: Audit entries, newest first. content: application/json: schema: $ref: '#/components/schemas/AuditList' default: $ref: '#/components/responses/Problem' /admin/connector-releases: get: operationId: listConnectorReleases summary: Edge connector releases stored for self-update description: > Releases are published by CI with `POST /api/v1/admin/connector-releases?version=&channel=¬es=` (`Authorization: Bearer `, the MSI as an `application/octet-stream` body of at most 64 MiB). That endpoint is not part of this portal contract; see docs/operations/edge.md (Connector releases). tags: - admin responses: '200': description: Releases, newest version first. content: application/json: schema: $ref: '#/components/schemas/ConnectorReleaseList' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: getTenant summary: Tenant profile tags: - tenant responses: '200': description: Tenant. content: application/json: schema: $ref: '#/components/schemas/Tenant' default: $ref: '#/components/responses/Problem' patch: operationId: updateTenant summary: Change the tenant's settings (the name is the Hub's) tags: - tenant requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateTenantRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/Tenant' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/dashboard: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: getTenantDashboard summary: Counters, controller health and today's traffic tags: - tenant responses: '200': description: Dashboard. content: application/json: schema: $ref: '#/components/schemas/TenantDashboard' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/users: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listTenantUsers summary: Tenant members and their roles (managed on Entrosity Hub) tags: - tenant responses: '200': description: Users. content: application/json: schema: $ref: '#/components/schemas/UserDirectory' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/sites: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listSites summary: Sites of the tenant tags: - tenant responses: '200': description: Sites. content: application/json: schema: $ref: '#/components/schemas/SiteList' default: $ref: '#/components/responses/Problem' post: operationId: createSite summary: Create a site tags: - tenant requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateSiteRequest' responses: '201': description: Created. content: application/json: schema: $ref: '#/components/schemas/Site' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/sites/{siteID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/SiteID' get: operationId: getSite summary: Get a site tags: - tenant responses: '200': description: Site. content: application/json: schema: $ref: '#/components/schemas/Site' default: $ref: '#/components/responses/Problem' patch: operationId: updateSite summary: Update a site (a new time zone resyncs its controllers) tags: - tenant requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateSiteRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/Site' default: $ref: '#/components/responses/Problem' delete: operationId: deleteSite summary: Delete a site (its connectors and controllers stay, without a site) tags: - tenant responses: '204': description: Deleted. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/audit: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listTenantAudit summary: Audit log of the tenant tags: - tenant parameters: - $ref: '#/components/parameters/Page' - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/AuditActor' - $ref: '#/components/parameters/AuditAction' - $ref: '#/components/parameters/AuditResourceType' - $ref: '#/components/parameters/AuditFrom' - $ref: '#/components/parameters/AuditTo' responses: '200': description: Audit entries, newest first. content: application/json: schema: $ref: '#/components/schemas/AuditList' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/stream: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: streamTenantEvents summary: Live updates (server-sent events) description: | `text/event-stream` of the tenant's live updates: `access.events` (new access log entries, an array), `controller.status`, `controller.sync`, `door.state`, `connector.status` and `job.update`. Authenticate with a bearer token or, for EventSource, `?sse_token=` from `POST /auth/sse-token`. tags: - events parameters: - name: sse_token in: query schema: type: string maxLength: 4096 responses: '200': description: Event stream. content: text/event-stream: schema: type: string default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/enrollment-tokens: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listEnrollmentTokens summary: Connector enrollment tokens of the tenant tags: - connectors responses: '200': description: Tokens, newest first. content: application/json: schema: $ref: '#/components/schemas/EnrollmentTokenList' default: $ref: '#/components/responses/Problem' post: operationId: createEnrollmentToken summary: Create a connector enrollment token (the secret is returned once) tags: - connectors requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateEnrollmentTokenRequest' responses: '201': description: Created. `secret` and the commands are shown only now. content: application/json: schema: $ref: '#/components/schemas/CreatedEnrollmentToken' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/enrollment-tokens/{tokenID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/TokenID' delete: operationId: revokeEnrollmentToken summary: Revoke an enrollment token tags: - connectors responses: '204': description: Revoked. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/enrollment-tokens/{tokenID}/delete: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/TokenID' post: operationId: deleteEnrollmentToken summary: >- Delete an enrollment token permanently (global admins; requires the password) description: | Global admins confirm with their own password: they send `step_up_token`, which Entrosity Hub issues for the password (`POST /api/platform/v1/auth/step-up`, product `edge`). tags: - connectors requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DeleteEnrollmentTokenRequest' responses: '204': description: Deleted. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/connectors: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listConnectors summary: Edge connectors of the tenant tags: - connectors responses: '200': description: Connectors. content: application/json: schema: $ref: '#/components/schemas/ConnectorList' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/connectors/{connectorID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/ConnectorID' get: operationId: getConnector summary: Get a connector tags: - connectors responses: '200': description: Connector. content: application/json: schema: $ref: '#/components/schemas/Connector' default: $ref: '#/components/responses/Problem' patch: operationId: updateConnector summary: Rename a connector or move it to a site tags: - connectors requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateConnectorRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/Connector' default: $ref: '#/components/responses/Problem' delete: operationId: deleteConnector summary: >- Remove a connector (409 connector_has_controllers while it drives controllers) tags: - connectors responses: '204': description: Removed. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/connectors/{connectorID}/discover: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/ConnectorID' post: operationId: discoverControllers summary: Look for controllers on the connector's network description: > Creates an `edge.discover` job; poll `GET /tenants/{tenantID}/jobs/{jobID}` (or watch `job.update`) for its result, a `DiscoverResult`. 409 connector_offline when the connector is not connected. tags: - connectors requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DiscoverRequest' responses: '202': description: Discovery started. content: application/json: schema: $ref: '#/components/schemas/Job' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/jobs/{jobID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/JobID' get: operationId: getJob summary: A connector job (discovery, test, door opening, configuration) tags: - connectors responses: '200': description: Job. content: application/json: schema: $ref: '#/components/schemas/Job' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/controllers: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listControllers summary: Access controllers tags: - controllers parameters: - name: connector_id in: query schema: type: string format: uuid - name: site_id in: query schema: type: string format: uuid responses: '200': description: Controllers. content: application/json: schema: $ref: '#/components/schemas/ControllerList' default: $ref: '#/components/responses/Problem' post: operationId: createController summary: Add a controller (its doors and readers are created from the door mode) tags: - controllers requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateControllerRequest' responses: '201': description: Created; the configuration is pushed right away. content: application/json: schema: $ref: '#/components/schemas/ControllerDetail' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/controllers/{controllerID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/ControllerID' get: operationId: getController summary: A controller with its doors and readers tags: - controllers responses: '200': description: Controller. content: application/json: schema: $ref: '#/components/schemas/ControllerDetail' default: $ref: '#/components/responses/Problem' patch: operationId: updateController summary: >- Rename, move to a site, change the bus address, PIN or doors layout, enable or disable description: > Only the fields sent change. A new door_mode rebuilds the doors and readers as creating the controller with that mode does: door 1 (with its id, name and timing) always stays, door 2 is added or removed, and the readers are wired to the new layout. 409 door_in_use (naming the access groups) when door 2 would be removed while access groups use it. enabled false disables the controller (open configuration jobs are cancelled, the connector gets edge.controller.remove); enabled true enables it and forces a resync. A disabled controller can still be edited (nothing is sent until it is enabled); opening its doors, testing it and resyncing it are refused with 409 controller_disabled. tags: - controllers requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateControllerRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/ControllerDetail' default: $ref: '#/components/responses/Problem' delete: operationId: deleteController summary: Remove a controller with its doors and readers tags: - controllers responses: '204': description: Removed. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/controllers/{controllerID}/sync: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/ControllerID' post: operationId: syncController summary: Send the controller's configuration again description: 409 controller_disabled when the controller is disabled. tags: - controllers responses: '202': description: Resync queued. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/controllers/{controllerID}/test: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/ControllerID' post: operationId: testController summary: Ask the connector to reach the controller (result is a ControllerInfo) description: 409 controller_disabled when the controller is disabled. tags: - controllers responses: '202': description: Test started. content: application/json: schema: $ref: '#/components/schemas/Job' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/controllers/{controllerID}/jobs: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/ControllerID' get: operationId: listControllerJobs summary: Recent jobs of the controller tags: - controllers responses: '200': description: Jobs, newest first. content: application/json: schema: $ref: '#/components/schemas/JobList' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/readers/{readerID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/ReaderID' patch: operationId: updateReader summary: Change which door a reader serves, its direction, name, or disable it tags: - controllers requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateReaderRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/Reader' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/doors: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listDoors summary: Doors of every controller tags: - doors parameters: - name: site_id in: query schema: type: string format: uuid responses: '200': description: Doors. content: application/json: schema: $ref: '#/components/schemas/DoorList' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/doors/{doorID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/DoorID' patch: operationId: updateDoor summary: Rename a door or change its lock relay and timing tags: - doors requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateDoorRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/Door' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/doors/{doorID}/open: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/DoorID' post: operationId: openDoor summary: Open a door remotely (for its pulse time) description: > 409 connector_offline when the controller's connector is not connected; 409 controller_disabled when the door's controller is disabled. tags: - doors responses: '202': description: Opening requested. content: application/json: schema: $ref: '#/components/schemas/Job' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/cardholders: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listCardholders summary: Cardholders tags: - people parameters: - $ref: '#/components/parameters/Page' - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/Query' - name: status in: query schema: $ref: '#/components/schemas/CardholderStatus' - name: group_id in: query schema: type: string format: uuid responses: '200': description: Cardholders. content: application/json: schema: $ref: '#/components/schemas/CardholderList' default: $ref: '#/components/responses/Problem' post: operationId: createCardholder summary: Add a cardholder tags: - people requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CardholderInput' responses: '201': description: Created. content: application/json: schema: $ref: '#/components/schemas/CardholderDetail' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/cardholders/{cardholderID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/CardholderID' get: operationId: getCardholder summary: A cardholder with their cards and groups tags: - people responses: '200': description: Cardholder. content: application/json: schema: $ref: '#/components/schemas/CardholderDetail' default: $ref: '#/components/responses/Problem' put: operationId: updateCardholder summary: Replace a cardholder's details (and, when given, groups) tags: - people requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CardholderInput' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/CardholderDetail' default: $ref: '#/components/responses/Problem' delete: operationId: deleteCardholder summary: Remove a cardholder (their cards return to the pool) tags: - people responses: '204': description: Removed. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/cards: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listCards summary: Cards (RFID and iButton credentials) description: > q matches the facility:number form, the card number, the 10-digit number printed on Wiegand 26 cards ((facility_code << 16) | card_number, with or without its leading zeros), the iButton id, the label and the cardholder's name. tags: - people parameters: - $ref: '#/components/parameters/Page' - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/Query' - name: cardholder_id in: query schema: type: string format: uuid - name: unassigned in: query description: Only cards without a cardholder (the pool). schema: type: boolean responses: '200': description: Cards, newest first. content: application/json: schema: $ref: '#/components/schemas/CardList' default: $ref: '#/components/responses/Problem' post: operationId: createCard summary: Register a card (409 card_already_registered for a duplicate number) tags: - people requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateCardRequest' responses: '201': description: Registered. content: application/json: schema: $ref: '#/components/schemas/Card' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/cards/{cardID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/CardID' patch: operationId: updateCard summary: Assign, unassign, block (suspended, lost) or reactivate a card tags: - people requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateCardRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/Card' default: $ref: '#/components/responses/Problem' delete: operationId: deleteCard summary: Delete a card tags: - people responses: '204': description: Deleted. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/schedules: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listSchedules summary: Weekly time schedules tags: - policy responses: '200': description: Schedules. content: application/json: schema: $ref: '#/components/schemas/ScheduleList' default: $ref: '#/components/responses/Problem' post: operationId: createSchedule summary: Create a schedule tags: - policy requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ScheduleInput' responses: '201': description: Created. content: application/json: schema: $ref: '#/components/schemas/Schedule' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/schedules/{scheduleID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/ScheduleID' get: operationId: getSchedule summary: A schedule tags: - policy responses: '200': description: Schedule. content: application/json: schema: $ref: '#/components/schemas/Schedule' default: $ref: '#/components/responses/Problem' put: operationId: updateSchedule summary: Replace a schedule (controllers using it are resynced) tags: - policy requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ScheduleInput' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/Schedule' default: $ref: '#/components/responses/Problem' delete: operationId: deleteSchedule summary: Delete a schedule (409 schedule_in_use while access groups use it) tags: - policy responses: '204': description: Deleted. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/holidays: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listHolidays summary: Holidays tags: - policy parameters: - name: from in: query schema: type: string format: date responses: '200': description: Holidays by date. content: application/json: schema: $ref: '#/components/schemas/HolidayList' default: $ref: '#/components/responses/Problem' post: operationId: createHoliday summary: Add a holiday (every controller is resynced) tags: - policy requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateHolidayRequest' responses: '201': description: Created. content: application/json: schema: $ref: '#/components/schemas/Holiday' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/holidays/{holidayID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/HolidayID' delete: operationId: deleteHoliday summary: Delete a holiday tags: - policy responses: '204': description: Deleted. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/access-groups: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listAccessGroups summary: Access groups tags: - policy responses: '200': description: Groups. content: application/json: schema: $ref: '#/components/schemas/AccessGroupList' default: $ref: '#/components/responses/Problem' post: operationId: createAccessGroup summary: Create an access group tags: - policy requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AccessGroupInput' responses: '201': description: Created. content: application/json: schema: $ref: '#/components/schemas/AccessGroupDetail' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/access-groups/{groupID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/GroupID' get: operationId: getAccessGroup summary: An access group with its doors and members tags: - policy responses: '200': description: Group. content: application/json: schema: $ref: '#/components/schemas/AccessGroupDetail' default: $ref: '#/components/responses/Problem' patch: operationId: updateAccessGroup summary: Rename an access group tags: - policy requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AccessGroupInput' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/AccessGroupDetail' default: $ref: '#/components/responses/Problem' delete: operationId: deleteAccessGroup summary: Delete an access group tags: - policy responses: '204': description: Deleted. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/access-groups/{groupID}/doors: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/GroupID' put: operationId: setAccessGroupDoors summary: Replace the group's doors, each with the schedule it is open for members tags: - policy requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SetGroupDoorsRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/AccessGroupDetail' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/access-groups/{groupID}/members: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/GroupID' post: operationId: changeAccessGroupMembers summary: Add and remove members tags: - policy requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ChangeMembersRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/AccessGroupDetail' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/events: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listAccessEvents summary: The access event log, newest first (keyset pagination) tags: - events parameters: - name: before_at in: query description: Cursor from the previous page (next_before_at). schema: type: string format: date-time - name: before_id in: query schema: type: string format: uuid - name: from in: query schema: type: string format: date-time - name: to in: query schema: type: string format: date-time - name: door_id in: query schema: type: string format: uuid - name: controller_id in: query schema: type: string format: uuid - name: cardholder_id in: query schema: type: string format: uuid - name: type in: query description: Event types (repeat the parameter for several). style: form explode: true schema: type: array maxItems: 20 items: type: string maxLength: 64 - name: limit in: query schema: type: integer minimum: 1 maximum: 500 default: 100 responses: '200': description: Events. content: application/json: schema: $ref: '#/components/schemas/AccessEventPage' default: $ref: '#/components/responses/Problem' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT parameters: TenantID: name: tenantID in: path required: true schema: type: string format: uuid SiteID: name: siteID in: path required: true schema: type: string format: uuid TokenID: name: tokenID in: path required: true schema: type: string format: uuid JobID: name: jobID in: path required: true schema: type: string format: uuid ConnectorID: name: connectorID in: path required: true schema: type: string format: uuid ControllerID: name: controllerID in: path required: true schema: type: string format: uuid ReaderID: name: readerID in: path required: true schema: type: string format: uuid DoorID: name: doorID in: path required: true schema: type: string format: uuid CardholderID: name: cardholderID in: path required: true schema: type: string format: uuid CardID: name: cardID in: path required: true schema: type: string format: uuid ScheduleID: name: scheduleID in: path required: true schema: type: string format: uuid HolidayID: name: holidayID in: path required: true schema: type: string format: uuid GroupID: name: groupID in: path required: true schema: type: string format: uuid Page: name: page in: query schema: type: integer minimum: 1 maximum: 100000 default: 1 PageSize: name: page_size in: query schema: type: integer minimum: 1 maximum: 200 default: 50 Query: name: q in: query schema: type: string maxLength: 200 AuditActor: name: actor_user_id in: query schema: type: string format: uuid AuditAction: name: action in: query schema: type: string maxLength: 100 AuditResourceType: name: resource_type in: query schema: type: string maxLength: 100 AuditFrom: name: from in: query schema: type: string format: date-time AuditTo: name: to in: query schema: type: string format: date-time responses: Problem: description: Error (RFC 7807). content: application/problem+json: schema: $ref: '#/components/schemas/Problem' schemas: Health: type: object required: - status properties: status: type: string enum: - ok Problem: type: object description: RFC 7807 problem details. required: - type - title - status properties: type: type: string description: URI reference identifying the problem type. example: about:blank title: type: string status: type: integer detail: type: string code: type: string description: Stable machine-readable error code, e.g. connector_offline. instance: type: string request_id: type: string fields: type: object description: Per-field validation errors. additionalProperties: type: string Role: type: string enum: - global_admin - tenant_admin - operator - viewer TenantRole: type: string enum: - tenant_admin - operator - viewer UserStatus: type: string enum: - active - disabled - deleted TenantStatus: type: string enum: - active - suspended User: type: object required: - id - email - display_name - role - tenant_id - status - created_at properties: id: type: string format: uuid email: type: string display_name: type: string role: $ref: '#/components/schemas/Role' tenant_id: type: string format: uuid nullable: true status: $ref: '#/components/schemas/UserStatus' created_at: type: string format: date-time Me: type: object description: >- The signed-in user. A user can belong to several tenants, with one role in each; global admins have every permission in every tenant. required: - id - email - display_name - status - is_global_admin - memberships - created_at properties: id: type: string format: uuid email: type: string display_name: type: string status: $ref: '#/components/schemas/UserStatus' is_global_admin: type: boolean memberships: type: array items: $ref: '#/components/schemas/TenantMembership' created_at: type: string format: date-time TenantMembership: type: object required: - tenant_id - tenant_name - tenant_status - role properties: tenant_id: type: string format: uuid tenant_name: type: string tenant_status: $ref: '#/components/schemas/TenantStatus' role: $ref: '#/components/schemas/TenantRole' UserDirectory: type: object required: - users properties: users: type: array items: $ref: '#/components/schemas/User' Tenant: type: object required: - id - name - slug - status - settings - created_at - updated_at properties: id: type: string format: uuid name: type: string slug: type: string status: $ref: '#/components/schemas/TenantStatus' settings: $ref: '#/components/schemas/TenantSettings' created_at: type: string format: date-time updated_at: type: string format: date-time TenantRetention: type: object additionalProperties: false description: Overrides of the server's retention windows (sent as a whole). properties: job_days: type: integer minimum: 7 maximum: 730 description: Finished connector jobs. audit_days: type: integer minimum: 30 maximum: 3650 description: Audit log entries. TenantSettings: type: object additionalProperties: false properties: default_timezone: type: string maxLength: 64 description: >- IANA time zone of the schedules of controllers without a site (default UTC). retention: $ref: '#/components/schemas/TenantRetention' TenantList: type: object required: - items - page - page_size - total properties: items: type: array items: $ref: '#/components/schemas/Tenant' page: type: integer page_size: type: integer total: type: integer UpdateTenantRequest: type: object additionalProperties: false properties: settings: $ref: '#/components/schemas/TenantSettings' AdminOverview: type: object required: - tenants_total - tenants_active - global_admins - tenant_users properties: tenants_total: type: integer tenants_active: type: integer global_admins: type: integer tenant_users: type: integer ConnectorRelease: type: object required: - id - version - channel - sha256 - size_bytes - signature - notes - created_at properties: id: type: string format: uuid version: type: string channel: $ref: '#/components/schemas/UpdateChannel' sha256: type: string size_bytes: type: integer format: int64 signature: type: string description: Ed25519 signature (base64) of the release manifest. notes: type: string created_at: type: string format: date-time ConnectorReleaseList: type: object required: - items - signing_enabled - public_key properties: items: type: array items: $ref: '#/components/schemas/ConnectorRelease' signing_enabled: type: boolean description: >- EDGE_RELEASE_SIGNING_KEY is set (without it nothing is published or rolled out). public_key: type: string description: >- The base64 Ed25519 public key connector builds must embed (RELEASE_PUBLIC_KEY); empty without a signing key. Site: type: object required: - id - tenant_id - name - description - timezone - created_at - updated_at properties: id: type: string format: uuid tenant_id: type: string format: uuid name: type: string description: type: string timezone: type: string description: IANA time zone e.g. Europe/Sofia.: null created_at: type: string format: date-time updated_at: type: string format: date-time SiteList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Site' CreateSiteRequest: type: object required: - name additionalProperties: false properties: name: type: string minLength: 1 maxLength: 200 description: type: string maxLength: 2000 timezone: type: string minLength: 1 maxLength: 64 UpdateSiteRequest: type: object additionalProperties: false properties: name: type: string minLength: 1 maxLength: 200 description: type: string maxLength: 2000 timezone: type: string minLength: 1 maxLength: 64 AuditEntry: type: object required: - id - ts - tenant_id - actor_user_id - actor_email - action - resource_type - resource_id - before - after - ip - request_id properties: id: type: string format: uuid ts: type: string format: date-time tenant_id: type: string format: uuid nullable: true actor_user_id: type: string format: uuid nullable: true actor_email: type: string action: type: string resource_type: type: string resource_id: type: string before: type: object nullable: true additionalProperties: true after: type: object nullable: true additionalProperties: true ip: type: string request_id: type: string AuditList: type: object required: - items - page - page_size - total properties: items: type: array items: $ref: '#/components/schemas/AuditEntry' page: type: integer page_size: type: integer total: type: integer TenantDashboard: type: object required: - connectors_total - connectors_online - controllers_total - controllers_online - controllers_pending - controllers_failed - doors_total - cardholders_active - cards_active - groups_total - sites_total - events_today - granted_today - denied_today - hourly properties: connectors_total: type: integer connectors_online: type: integer controllers_total: type: integer description: Enabled controllers (disabled ones are not counted). controllers_online: type: integer controllers_pending: type: integer description: Configuration waiting to be applied. controllers_failed: type: integer description: Configuration could not be applied. doors_total: type: integer cardholders_active: type: integer cards_active: type: integer groups_total: type: integer sites_total: type: integer events_today: type: integer description: Events in the last 24 hours. granted_today: type: integer denied_today: type: integer description: Denied and unknown cards in the last 24 hours. hourly: type: array description: Access decisions per hour over the last 24 hours. items: $ref: '#/components/schemas/HourlyCount' HourlyCount: type: object required: - hour - granted - denied properties: hour: type: string format: date-time granted: type: integer denied: type: integer EnrollmentTokenStatus: type: string enum: - active - revoked - expired - exhausted EnrollmentToken: type: object required: - id - tenant_id - site_id - label - max_uses - uses - expires_at - revoked_at - created_by - created_by_email - created_at - status properties: id: type: string format: uuid tenant_id: type: string format: uuid site_id: type: string format: uuid nullable: true label: type: string max_uses: type: integer nullable: true uses: type: integer expires_at: type: string format: date-time nullable: true revoked_at: type: string format: date-time nullable: true created_by: type: string format: uuid nullable: true created_by_email: type: string created_at: type: string format: date-time status: $ref: '#/components/schemas/EnrollmentTokenStatus' EnrollmentTokenList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/EnrollmentToken' CreateEnrollmentTokenRequest: type: object additionalProperties: false properties: label: type: string maxLength: 200 site_id: type: string format: uuid expires_at: type: string format: date-time max_uses: type: integer minimum: 1 maximum: 100000 CreatedEnrollmentToken: type: object required: - token - secret - install_command - cli_command - download_url properties: token: $ref: '#/components/schemas/EnrollmentToken' secret: type: string description: The raw token. Shown only once. install_command: type: string description: msiexec one-liner for a silent installation. cli_command: type: string description: Enrolls an installed connector from the command line. download_url: type: string description: Where the connector installer can be downloaded (may be empty). DeleteEnrollmentTokenRequest: type: object required: - step_up_token additionalProperties: false properties: step_up_token: type: string minLength: 1 maxLength: 4096 Connector: type: object required: - id - site_id - name - hostname - domain - version - capabilities - status - last_seen_at - controller_count - update_channel - update_version - update_offered_at - created_at properties: id: type: string format: uuid site_id: type: string format: uuid nullable: true name: type: string hostname: type: string domain: type: string version: type: string capabilities: type: array items: type: string description: >- Advertised in the connector's hello; edge_update means it updates itself. status: type: string enum: - online - offline last_seen_at: type: string format: date-time nullable: true controller_count: type: integer update_channel: $ref: '#/components/schemas/UpdateChannel' update_version: type: string nullable: true description: The release last offered to the connector. update_offered_at: type: string format: date-time nullable: true created_at: type: string format: date-time UpdateChannel: type: string enum: - stable - beta description: >- Which releases the connector updates to (beta also receives stable releases). ConnectorList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Connector' UpdateConnectorRequest: type: object additionalProperties: false properties: name: type: string maxLength: 200 site_id: type: string format: uuid clear_site: type: boolean description: Remove the connector from its site. update_channel: $ref: '#/components/schemas/UpdateChannel' Driver: type: string enum: - simulator - trackbase002 description: simulator is a software controller for trials and tests. Transport: type: string enum: - tcp - rs485 DoorMode: type: string enum: - one_bidirectional - two_unidirectional DiscoverRequest: type: object required: - driver additionalProperties: false properties: driver: $ref: '#/components/schemas/Driver' transport: $ref: '#/components/schemas/Transport' addresses: type: array maxItems: 256 description: host:port addresses to probe (TrackBase); empty for the simulator. items: type: string maxLength: 255 DiscoveredController: type: object required: - driver - target - model - serial - firmware properties: driver: type: string target: $ref: '#/components/schemas/Target' model: type: string serial: type: string firmware: type: string door_mode: type: string DiscoverResult: type: object required: - controllers properties: controllers: type: array items: $ref: '#/components/schemas/DiscoveredController' Target: type: object required: - transport - address properties: transport: type: string address: type: string unit_id: type: integer JobStatus: type: string enum: - created - sent - acked - running - succeeded - failed - timeout - cancelled Job: type: object required: - id - connector_id - controller_id - type - status - created_by - created_at - sent_at - finished_at - progress_pct - progress_message - error_code - error - result properties: id: type: string format: uuid connector_id: type: string format: uuid controller_id: type: string format: uuid nullable: true type: type: string status: $ref: '#/components/schemas/JobStatus' created_by: type: string format: uuid nullable: true created_at: type: string format: date-time sent_at: type: string format: date-time nullable: true finished_at: type: string format: date-time nullable: true progress_pct: type: integer nullable: true progress_message: type: string error_code: type: string error: type: string result: type: object nullable: true additionalProperties: true description: The job's result payload (e.g. a DiscoverResult), when it succeeded. JobList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Job' ControllerStatus: type: string enum: - unknown - online - offline - error SyncStatus: type: string description: >- disabled while the controller is disabled (enabled false); the other values are for enabled controllers. enum: - in_sync - pending - syncing - failed - disabled Controller: type: object required: - id - connector_id - connector_name - connector_status - site_id - site_name - name - driver - transport - address - unit_id - door_mode - model - serial - firmware - status - status_error - last_seen_at - desired_version - applied_version - sync_status - sync_error_code - sync_error - synced_at - card_count - pin_set - enabled - created_at properties: id: type: string format: uuid connector_id: type: string format: uuid connector_name: type: string connector_status: type: string site_id: type: string format: uuid nullable: true site_name: type: string name: type: string driver: $ref: '#/components/schemas/Driver' transport: $ref: '#/components/schemas/Transport' address: type: string unit_id: type: integer door_mode: $ref: '#/components/schemas/DoorMode' model: type: string serial: type: string firmware: type: string status: $ref: '#/components/schemas/ControllerStatus' status_error: type: string last_seen_at: type: string format: date-time nullable: true desired_version: type: integer format: int64 applied_version: type: integer format: int64 sync_status: $ref: '#/components/schemas/SyncStatus' sync_error_code: type: string sync_error: type: string synced_at: type: string format: date-time nullable: true card_count: type: integer description: Cards the controller holds (or would hold). pin_set: type: boolean description: A PIN is set (the PIN itself is never returned). enabled: type: boolean description: > false: Edge and its connector leave the controller alone (no configuration, polling, events or clock setting); sync_status is disabled and status reports for it are ignored. created_at: type: string format: date-time ControllerList: type: object required: - items - capacity properties: items: type: array items: $ref: '#/components/schemas/Controller' capacity: type: integer description: Cards one controller holds. ControllerDetail: type: object required: - controller - doors - readers properties: controller: $ref: '#/components/schemas/Controller' doors: type: array items: $ref: '#/components/schemas/Door' readers: type: array items: $ref: '#/components/schemas/Reader' CreateControllerRequest: type: object required: - connector_id - name - driver - address - door_mode additionalProperties: false properties: connector_id: type: string format: uuid site_id: type: string format: uuid name: type: string minLength: 1 maxLength: 200 driver: $ref: '#/components/schemas/Driver' transport: $ref: '#/components/schemas/Transport' address: type: string minLength: 1 maxLength: 255 description: host:port (tcp) serial port or gateway (rs485).: null unit_id: type: integer minimum: 0 maximum: 31 description: RS-485 bus address (1-31). door_mode: $ref: '#/components/schemas/DoorMode' model: type: string maxLength: 100 serial: type: string maxLength: 100 firmware: type: string maxLength: 100 pin: $ref: '#/components/schemas/ControllerPIN' ControllerPIN: type: integer format: int64 minimum: 1 maximum: 4294967294 writeOnly: true description: >- The controller's PIN (TrackBase002 only; 422 for other drivers). Write-only. UpdateControllerRequest: type: object additionalProperties: false properties: name: type: string minLength: 1 maxLength: 200 site_id: type: string format: uuid clear_site: type: boolean description: Remove the controller from its site. transport: $ref: '#/components/schemas/Transport' address: type: string minLength: 1 maxLength: 255 unit_id: type: integer minimum: 0 maximum: 31 pin: $ref: '#/components/schemas/ControllerPIN' clear_pin: type: boolean description: Remove the controller's PIN (not together with pin). door_mode: $ref: '#/components/schemas/DoorMode' enabled: type: boolean description: > false disables the controller: its pending configurations are cancelled and its connector is told to stop driving it (edge.controller.remove). true enables it again and forces a resync. ReaderChannel: type: string enum: - wiegand1 - wiegand2 - ibutton1 - ibutton2 Direction: type: string enum: - in - out Reader: type: object required: - id - controller_id - door_id - channel - direction - name - enabled properties: id: type: string format: uuid controller_id: type: string format: uuid door_id: type: string format: uuid channel: $ref: '#/components/schemas/ReaderChannel' direction: $ref: '#/components/schemas/Direction' name: type: string enabled: type: boolean UpdateReaderRequest: type: object additionalProperties: false properties: door_id: type: string format: uuid direction: $ref: '#/components/schemas/Direction' name: type: string maxLength: 200 enabled: type: boolean Door: type: object required: - id - controller_id - index - name - lock_relay - open_pulse_ms - held_open_seconds - is_open - is_locked - state_at properties: id: type: string format: uuid controller_id: type: string format: uuid controller_name: type: string controller_status: $ref: '#/components/schemas/ControllerStatus' site_id: type: string format: uuid nullable: true site_name: type: string index: type: integer description: 1 or 2. name: type: string lock_relay: type: integer open_pulse_ms: type: integer held_open_seconds: type: integer is_open: type: boolean nullable: true description: Last reported state (null = unknown). is_locked: type: boolean nullable: true state_at: type: string format: date-time nullable: true DoorList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Door' UpdateDoorRequest: type: object additionalProperties: false properties: name: type: string minLength: 1 maxLength: 200 lock_relay: type: integer minimum: 1 maximum: 4 open_pulse_ms: type: integer minimum: 100 maximum: 60000 held_open_seconds: type: integer minimum: 0 maximum: 3600 CardholderStatus: type: string enum: - active - suspended Cardholder: type: object required: - id - first_name - last_name - email - phone - department - external_ref - notes - status - valid_from - valid_to - card_count - group_names - created_at - updated_at properties: id: type: string format: uuid first_name: type: string last_name: type: string email: type: string phone: type: string department: type: string external_ref: type: string description: Employee number or another reference. notes: type: string status: $ref: '#/components/schemas/CardholderStatus' valid_from: type: string format: date-time nullable: true valid_to: type: string format: date-time nullable: true card_count: type: integer group_names: type: array items: type: string created_at: type: string format: date-time updated_at: type: string format: date-time CardholderList: type: object required: - items - page - page_size - total properties: items: type: array items: $ref: '#/components/schemas/Cardholder' page: type: integer page_size: type: integer total: type: integer CardholderDetail: type: object required: - cardholder - cards - groups properties: cardholder: $ref: '#/components/schemas/Cardholder' cards: type: array items: $ref: '#/components/schemas/Card' groups: type: array items: $ref: '#/components/schemas/AccessGroup' CardholderInput: type: object required: - first_name additionalProperties: false properties: first_name: type: string minLength: 1 maxLength: 100 last_name: type: string maxLength: 100 email: type: string maxLength: 254 phone: type: string maxLength: 50 department: type: string maxLength: 100 external_ref: type: string maxLength: 100 notes: type: string maxLength: 2000 status: $ref: '#/components/schemas/CardholderStatus' valid_from: type: string format: date-time nullable: true valid_to: type: string format: date-time nullable: true group_ids: type: array maxItems: 200 description: Replaces the cardholder's access groups when present. items: type: string format: uuid CredentialKind: type: string enum: - wiegand26 - ibutton CardStatus: type: string enum: - active - suspended - lost Card: type: object required: - id - cardholder_id - cardholder_name - kind - facility_code - card_number - ibutton_id - display - status - label - created_at properties: id: type: string format: uuid cardholder_id: type: string format: uuid nullable: true cardholder_name: type: string kind: $ref: '#/components/schemas/CredentialKind' facility_code: type: integer nullable: true card_number: type: integer nullable: true ibutton_id: type: string nullable: true display: type: string description: 'Human form: 12:3456 or the iButton id.' status: $ref: '#/components/schemas/CardStatus' label: type: string created_at: type: string format: date-time CardList: type: object required: - items - page - page_size - total properties: items: type: array items: $ref: '#/components/schemas/Card' page: type: integer page_size: type: integer total: type: integer CreateCardRequest: type: object required: - kind additionalProperties: false properties: kind: $ref: '#/components/schemas/CredentialKind' facility_code: type: integer minimum: 0 maximum: 255 card_number: type: integer minimum: 0 maximum: 65535 ibutton_id: type: string pattern: ^[0-9A-Fa-f]{2,16}$ cardholder_id: type: string format: uuid status: $ref: '#/components/schemas/CardStatus' label: type: string maxLength: 100 UpdateCardRequest: type: object additionalProperties: false properties: cardholder_id: type: string format: uuid description: Assign the card to this cardholder. unassign: type: boolean description: Return the card to the pool of unassigned cards. status: $ref: '#/components/schemas/CardStatus' label: type: string maxLength: 100 Interval: type: object required: - day - start - end additionalProperties: false properties: day: type: integer minimum: 0 maximum: 7 description: 0 = Monday … 6 = Sunday, 7 = holidays. start: type: integer minimum: 0 maximum: 1439 description: Minutes since midnight. end: type: integer minimum: 1 maximum: 1440 description: Minutes since midnight (exclusive). Schedule: type: object required: - id - name - description - intervals - usage_count - created_at - updated_at properties: id: type: string format: uuid name: type: string description: type: string intervals: type: array items: $ref: '#/components/schemas/Interval' usage_count: type: integer description: Group doors using the schedule. created_at: type: string format: date-time updated_at: type: string format: date-time ScheduleList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Schedule' ScheduleInput: type: object required: - name - intervals additionalProperties: false properties: name: type: string minLength: 1 maxLength: 100 description: type: string maxLength: 500 intervals: type: array maxItems: 64 items: $ref: '#/components/schemas/Interval' Holiday: type: object required: - id - date - name properties: id: type: string format: uuid date: type: string format: date name: type: string HolidayList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Holiday' CreateHolidayRequest: type: object required: - date - name additionalProperties: false properties: date: type: string format: date name: type: string minLength: 1 maxLength: 100 AccessGroup: type: object required: - id - name - description - member_count - door_count - created_at - updated_at properties: id: type: string format: uuid name: type: string description: type: string member_count: type: integer door_count: type: integer created_at: type: string format: date-time updated_at: type: string format: date-time AccessGroupList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/AccessGroup' GroupDoor: type: object required: - door_id - door_name - controller_name - schedule_id - schedule_name properties: door_id: type: string format: uuid door_name: type: string controller_name: type: string schedule_id: type: string format: uuid schedule_name: type: string AccessGroupDetail: type: object required: - group - doors - members properties: group: $ref: '#/components/schemas/AccessGroup' doors: type: array items: $ref: '#/components/schemas/GroupDoor' members: type: array items: $ref: '#/components/schemas/Cardholder' AccessGroupInput: type: object required: - name additionalProperties: false properties: name: type: string minLength: 1 maxLength: 100 description: type: string maxLength: 500 SetGroupDoorsRequest: type: object required: - doors additionalProperties: false properties: doors: type: array maxItems: 500 items: type: object required: - door_id - schedule_id additionalProperties: false properties: door_id: type: string format: uuid schedule_id: type: string format: uuid ChangeMembersRequest: type: object additionalProperties: false properties: add: type: array maxItems: 1000 items: type: string format: uuid remove: type: array maxItems: 1000 items: type: string format: uuid AccessEvent: type: object required: - id - occurred_at - type properties: id: type: string format: uuid occurred_at: type: string format: date-time type: type: string description: | access_granted, access_denied (reason outside_schedule, no_access, holiday), unknown_card, door_opened_remote, door_forced, door_held_open, door_closed, controller_online, controller_offline, config_applied, tamper. reason: type: string controller_id: type: string format: uuid nullable: true controller_name: type: string door_id: type: string format: uuid nullable: true door_name: type: string card_id: type: string format: uuid nullable: true cardholder_id: type: string format: uuid nullable: true cardholder_name: type: string credential: type: string direction: type: string detail: type: string actor_email: type: string AccessEventPage: type: object required: - items - has_more properties: items: type: array items: $ref: '#/components/schemas/AccessEvent' has_more: type: boolean next_before_at: type: string format: date-time next_before_id: type: string format: uuid