openapi: 3.0.3 info: title: Entrosity Sphere Portal API version: 0.1.0 description: > Entrosity Sphere portal REST API: video management for network video recorders (Dahua NVRs) - live view, playback, PTZ and alarms. 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 Sphere (`POST /api/platform/v1/auth/product-token {"product":"sphere"}`, with the Hub's session cookie). Users, tenants (the Hub's organizations) and roles are managed on the Hub; Sphere 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: nvrs - name: cameras - name: live - name: playback - name: alarms - name: views - 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/connector-releases: get: operationId: listAdminConnectorReleases summary: Stored connector releases, with download links description: >- Every stored Sphere connector installer (the newest ten), newest first, each with a link that needs no sign-in until `download_expires_at`; `current` marks the release connectors are updated to (SPHERE_CONNECTOR_UPDATE_CHANNEL). `adoption` counts the enrolled connectors by version. Empty when releases are off. tags: - admin responses: '200': description: Releases. content: application/json: schema: $ref: '#/components/schemas/AdminConnectorReleaseList' 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' /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, NVR health and the last day's alarms 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 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 NVRs 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: `alarm.new` (new alarms, an array), `alarm.ack`, `nvr.status`, `stream.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 `sphere`). tags: - connectors requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DeleteEnrollmentTokenRequest' responses: '204': description: Deleted. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/connector-release: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: getConnectorRelease summary: The newest connector installer, with a download link description: >- The release connectors are updated to (SPHERE_CONNECTOR_UPDATE_CHANNEL). `download_url` needs no sign-in and works until `download_expires_at`. 404 when no release has been published. tags: - connectors responses: '200': description: The newest release. content: application/json: schema: $ref: '#/components/schemas/ConnectorRelease' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/connectors: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listConnectors summary: Sphere 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_nvrs while it drives NVRs) tags: - connectors responses: '204': description: Removed. 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 (NVR test, recordings search, PTZ, streams) tags: - connectors responses: '200': description: Job. content: application/json: schema: $ref: '#/components/schemas/Job' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/nvrs: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listNvrs summary: The tenant's NVRs tags: - nvrs parameters: - name: site_id in: query schema: type: string format: uuid responses: '200': description: NVRs. content: application/json: schema: $ref: '#/components/schemas/NvrList' default: $ref: '#/components/responses/Problem' post: operationId: createNvr summary: Add an NVR behind a connector (connects to it unless connect is false) description: | The password is stored encrypted and never returned. 409 nvr_address_taken when the connector already drives an NVR at that address and port. tags: - nvrs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateNvrRequest' responses: '201': description: Added. content: application/json: schema: $ref: '#/components/schemas/NvrDetail' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/nvrs/{nvrID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/NvrID' get: operationId: getNvr summary: An NVR with its cameras tags: - nvrs responses: '200': description: NVR. content: application/json: schema: $ref: '#/components/schemas/NvrDetail' default: $ref: '#/components/responses/Problem' patch: operationId: updateNvr summary: Rename an NVR, move it to a site or change its address tags: - nvrs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateNvrRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/Nvr' default: $ref: '#/components/responses/Problem' delete: operationId: deleteNvr summary: Remove an NVR (the connector signs out and forgets it) tags: - nvrs responses: '204': description: Removed. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/nvrs/{nvrID}/credentials: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/NvrID' put: operationId: setNvrCredentials summary: Change the user name and password the connector signs in with tags: - nvrs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/NvrCredentials' responses: '200': description: Stored; the connector signs in again. content: application/json: schema: $ref: '#/components/schemas/Nvr' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/nvrs/{nvrID}/connect: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/NvrID' post: operationId: connectNvr summary: Sign in to the NVR (stay connected, receive alarms, serve video) tags: - nvrs responses: '200': description: Connecting. content: application/json: schema: $ref: '#/components/schemas/Nvr' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/nvrs/{nvrID}/disconnect: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/NvrID' post: operationId: disconnectNvr summary: Sign out of the NVR (its streams stop) tags: - nvrs responses: '200': description: Disconnecting. content: application/json: schema: $ref: '#/components/schemas/Nvr' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/nvrs/{nvrID}/test: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/NvrID' post: operationId: testNvr summary: Sign in once, describe the NVR and refresh its cameras description: > Creates an `sphere.nvr.test` job; poll `GET /tenants/{tenantID}/jobs/{jobID}` (or watch `job.update`) for its result, an `NvrTestResult`. On success the NVR's details and cameras are updated. 409 connector_offline when the connector is not connected. tags: - nvrs responses: '202': description: Test started. content: application/json: schema: $ref: '#/components/schemas/Job' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/nvrs/{nvrID}/tune-live: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/NvrID' post: operationId: tuneNvrLive summary: >- Shorten the cameras' sub-stream keyframe interval for a quicker live picture description: | Changes the NVR's settings: every camera's sub stream (the one live grids show) gets a keyframe at least every `keyframe_seconds` (its frame rate × that many frames). A viewer joining a stream waits for the next keyframe before the picture shows, so this bounds the wait. Main streams, which the NVR records, are never changed, and shorter intervals are kept. Each channel is then checked in its sub stream: a camera that keeps a longer interval than the NVR stores is reported as not changed. Creates an `sphere.nvr.tune_live` job; its result is an `NvrLiveTuneResult`. 409 connector_offline when the connector is not connected. tags: - nvrs requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/TuneNvrLiveRequest' responses: '202': description: Tuning started. content: application/json: schema: $ref: '#/components/schemas/Job' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/nvrs/{nvrID}/jobs: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/NvrID' get: operationId: listNvrJobs summary: The NVR's recent connector jobs tags: - nvrs responses: '200': description: Jobs. content: application/json: schema: $ref: '#/components/schemas/JobList' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/cameras: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listCameras summary: The tenant's cameras (every NVR's channels) tags: - cameras parameters: - name: nvr_id in: query schema: type: string format: uuid - name: site_id in: query schema: type: string format: uuid responses: '200': description: Cameras. content: application/json: schema: $ref: '#/components/schemas/CameraList' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/cameras/{cameraID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/CameraID' patch: operationId: updateCamera summary: Rename a camera or hide it tags: - cameras requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateCameraRequest' responses: '200': description: Updated. content: application/json: schema: $ref: '#/components/schemas/Camera' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/cameras/{cameraID}/ptz: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/CameraID' post: operationId: ptzCamera summary: Move a PTZ camera (start and stop a movement, go to or store a preset) description: | A movement runs from `start` until `stop` (hold-to-move); presets use `start` only. Cameras the NVR does not report as PTZ are not refused (detection is best effort). 409 nvr_offline or connector_offline. Commands expire after 5 seconds, so a late one never moves the camera. tags: - cameras requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PtzRequest' responses: '202': description: Sent. content: application/json: schema: $ref: '#/components/schemas/Job' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/cameras/{cameraID}/recordings: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/CameraID' post: operationId: findRecordings summary: Search the camera's recordings on the NVR description: | Creates an `sphere.recordings.find` job; its result is a `RecordingsResult`. At most 7 days per search. tags: - playback requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RecordingsRequest' responses: '202': description: Search started. content: application/json: schema: $ref: '#/components/schemas/Job' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/streams: parameters: - $ref: '#/components/parameters/TenantID' post: operationId: openStream summary: Watch a camera live or play back a recording description: | Returns a viewer session: play `whep_url` (WHEP) with `Authorization: Bearer ` and keep the session alive with `POST /streams/{streamID}/keepalive` every 15 seconds (it returns a fresh token). Live streams are shared by every viewer of the camera's profile; a playback is the viewer's own. Unwatched streams stop after 30 seconds. 409 stream_limit when the site's or the NVR's limit is reached, nvr_offline, connector_offline, camera_disabled. tags: - live requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OpenStreamRequest' responses: '201': description: Viewer session. content: application/json: schema: $ref: '#/components/schemas/StreamSession' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/streams/{streamID}/keepalive: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/StreamID' post: operationId: keepStreamAlive summary: Keep watching (every 15 seconds); returns the state and a fresh token tags: - live responses: '200': description: Viewer session. content: application/json: schema: $ref: '#/components/schemas/StreamSession' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/streams/{streamID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/StreamID' delete: operationId: closeStream summary: Stop watching tags: - live responses: '204': description: Closed. default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/alarms: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listAlarms summary: The alarm log, newest first (keyset pagination) tags: - alarms 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: nvr_id in: query schema: type: string format: uuid - name: camera_id in: query schema: type: string format: uuid - name: acked in: query schema: type: boolean - name: code in: query description: Alarm codes (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: Alarms. content: application/json: schema: $ref: '#/components/schemas/AlarmPage' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/alarms/ack: parameters: - $ref: '#/components/parameters/TenantID' post: operationId: ackAlarms summary: Acknowledge alarms (by id, or everything up to a moment) tags: - alarms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AckAlarmsRequest' responses: '200': description: Acknowledged. content: application/json: schema: $ref: '#/components/schemas/AckAlarmsResult' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/views: parameters: - $ref: '#/components/parameters/TenantID' get: operationId: listViews summary: Saved camera grids (shared ones and the user's own) tags: - views responses: '200': description: Views. content: application/json: schema: $ref: '#/components/schemas/ViewList' default: $ref: '#/components/responses/Problem' post: operationId: createView summary: Save a camera grid tags: - views requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ViewInput' responses: '201': description: Saved. content: application/json: schema: $ref: '#/components/schemas/SavedView' default: $ref: '#/components/responses/Problem' /tenants/{tenantID}/views/{viewID}: parameters: - $ref: '#/components/parameters/TenantID' - $ref: '#/components/parameters/ViewID' put: operationId: updateView summary: Change a saved grid (shared grids need views:manage) tags: - views requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ViewInput' responses: '200': description: Saved. content: application/json: schema: $ref: '#/components/schemas/SavedView' default: $ref: '#/components/responses/Problem' delete: operationId: deleteView summary: Delete a saved grid tags: - views responses: '204': description: Deleted. 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 NvrID: name: nvrID in: path required: true schema: type: string format: uuid CameraID: name: cameraID in: path required: true schema: type: string format: uuid StreamID: name: streamID in: path required: true schema: type: string format: uuid ViewID: name: viewID 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 shown for NVRs 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 - nvrs_total - nvrs_online - nvrs_failing - cameras_total - connectors_total - connectors_online - alarms_unacked - streams_active - tenants properties: tenants_total: type: integer tenants_active: type: integer global_admins: type: integer tenant_users: type: integer nvrs_total: type: integer nvrs_online: type: integer nvrs_failing: type: integer description: Wanted connected but offline, refused or unsupported. cameras_total: type: integer connectors_total: type: integer description: Not revoked. connectors_online: type: integer alarms_unacked: type: integer description: Unacknowledged alarms of the last 24 hours. streams_active: type: integer description: Streams being published right now. tenants: type: array items: $ref: '#/components/schemas/TenantVideoCounts' TenantVideoCounts: type: object required: - tenant_id - name - status - nvrs_total - nvrs_online - nvrs_failing - cameras_total - connectors_total - connectors_online properties: tenant_id: type: string format: uuid name: type: string status: type: string nvrs_total: type: integer nvrs_online: type: integer nvrs_failing: type: integer cameras_total: type: integer connectors_total: type: integer connectors_online: type: integer AdminConnectorReleaseList: type: object required: - items - adoption - update_channel - releases_enabled properties: items: type: array items: $ref: '#/components/schemas/AdminConnectorRelease' adoption: type: array items: $ref: '#/components/schemas/ConnectorVersionCount' update_channel: type: string enum: - stable - dev releases_enabled: type: boolean description: SPHERE_RELEASE_SIGNING_KEY is set. public_key: type: string description: Base64 Ed25519 key connectors verify releases with. AdminConnectorRelease: type: object required: - id - version - channel - sha256 - size_bytes - created_at - current - file_name - download_url - download_expires_at properties: id: type: string format: uuid version: type: string channel: type: string enum: - stable - dev sha256: type: string size_bytes: type: integer format: int64 notes: type: string created_at: type: string format: date-time current: type: boolean description: The release connectors are updated to. file_name: type: string download_url: type: string description: The installer (no sign-in needed until it expires). download_expires_at: type: string format: date-time ConnectorVersionCount: type: object required: - version - count properties: version: type: string description: Empty when the connector has not reported one. count: type: integer 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 - nvrs_total - nvrs_online - nvrs_failing - cameras_total - cameras_enabled - streams_active - sites_total - alarms_today - alarms_unacked properties: connectors_total: type: integer connectors_online: type: integer nvrs_total: type: integer nvrs_online: type: integer nvrs_failing: type: integer description: Wanted connected but offline refusing the sign-in or unsupported.: null cameras_total: type: integer cameras_enabled: type: integer streams_active: type: integer description: Streams being relayed now. sites_total: type: integer alarms_today: type: integer description: Alarms in the last 24 hours. alarms_unacked: type: integer description: Unacknowledged alarms in the last 24 hours. 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). ConnectorRelease: type: object required: - version - channel - sha256 - size_bytes - created_at - download_url - download_expires_at properties: version: type: string example: 0.1.0 channel: type: string enum: - stable - dev sha256: type: string size_bytes: type: integer format: int64 notes: type: string created_at: type: string format: date-time download_url: type: string description: The installer (no sign-in needed until it expires). download_expires_at: type: string format: date-time 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 - nvr_count - 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 status: type: string enum: - online - offline last_seen_at: type: string format: date-time nullable: true nvr_count: type: integer created_at: type: string format: date-time 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. JobStatus: type: string enum: - created - sent - acked - running - succeeded - failed - timeout - cancelled Job: type: object required: - id - connector_id - nvr_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 nvr_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 (an NvrTestResult, a RecordingsResult), when it succeeded. JobList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Job' NvrDriver: type: string enum: - dahua - simulator description: >- simulator is a software NVR built into the connector, for trials and tests. NvrState: type: string enum: - connected - disconnected description: What users asked for (signed in or not). NvrStatus: type: string enum: - unknown - online - offline - auth_failed - unsupported - disconnected description: What the connector last reported. Nvr: type: object required: - id - site_id - connector_id - name - driver - host - port - https - rtsp_port - timezone - username - desired_state - status - status_error - device_type - serial - firmware - channel_count - camera_count - last_seen_at - created_at - updated_at properties: id: type: string format: uuid site_id: type: string format: uuid nullable: true connector_id: type: string format: uuid name: type: string driver: $ref: '#/components/schemas/NvrDriver' host: type: string port: type: integer https: type: boolean rtsp_port: type: integer timezone: type: string description: IANA zone of the NVR's clock (empty - the connector's). username: type: string desired_state: $ref: '#/components/schemas/NvrState' status: $ref: '#/components/schemas/NvrStatus' status_error: type: string device_type: type: string serial: type: string firmware: type: string channel_count: type: integer camera_count: type: integer last_seen_at: type: string format: date-time nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time NvrList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Nvr' NvrDetail: type: object required: - nvr - cameras properties: nvr: $ref: '#/components/schemas/Nvr' cameras: type: array items: $ref: '#/components/schemas/Camera' CreateNvrRequest: type: object required: - connector_id - name - driver - host - username - password 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/NvrDriver' host: type: string minLength: 1 maxLength: 255 pattern: ^[A-Za-z0-9.:\[\]-]+$ port: type: integer minimum: 1 maximum: 65535 default: 80 https: type: boolean default: false rtsp_port: type: integer minimum: 1 maximum: 65535 default: 554 timezone: type: string maxLength: 64 username: type: string minLength: 1 maxLength: 128 password: type: string maxLength: 256 connect: type: boolean default: true description: Sign in right away. UpdateNvrRequest: type: object additionalProperties: false properties: name: type: string minLength: 1 maxLength: 200 site_id: type: string format: uuid clear_site: type: boolean host: type: string minLength: 1 maxLength: 255 pattern: ^[A-Za-z0-9.:\[\]-]+$ port: type: integer minimum: 1 maximum: 65535 https: type: boolean rtsp_port: type: integer minimum: 1 maximum: 65535 timezone: type: string maxLength: 64 description: Empty clears it. NvrCredentials: type: object required: - username - password additionalProperties: false properties: username: type: string minLength: 1 maxLength: 128 password: type: string maxLength: 256 TuneNvrLiveRequest: type: object additionalProperties: false properties: keyframe_seconds: type: integer minimum: 1 maximum: 4 default: 1 NvrLiveTuneResult: type: object required: - channels description: The result of an sphere.nvr.tune_live job. properties: channels: type: array items: type: object required: - channel - changed properties: channel: type: integer fps: type: integer description: The sub stream's frame rate. gop_before: type: integer description: Keyframe interval before in frames.: null gop_after: type: integer description: Keyframe interval now in frames.: null changed: type: boolean description: >- The new interval is in effect in the stream. False without error: it was already short enough. error: type: string measured_ms: type: integer description: >- The keyframe interval measured in the sub stream afterwards (an NVR may keep a setting its camera does not apply). Absent: it could not be measured. NvrTestResult: type: object required: - info - rtsp_reachable description: The result of an sphere.nvr.test job. properties: info: type: object additionalProperties: true rtsp_reachable: type: boolean warnings: type: array items: type: string Camera: type: object required: - id - nvr_id - nvr_name - site_id - channel - name - title - label - enabled - ptz - online - main_codec - sub_codec - nvr_status properties: id: type: string format: uuid nvr_id: type: string format: uuid nvr_name: type: string site_id: type: string format: uuid nullable: true channel: type: integer description: 1-based as on the NVR.: null name: type: string description: The label else the NVR's title: null else "Channel N".: null title: type: string description: The channel name on the NVR. label: type: string description: The user's name for it (empty - use the title). enabled: type: boolean ptz: type: boolean online: type: boolean description: False when the NVR reports video loss. main_codec: type: string sub_codec: type: string nvr_status: $ref: '#/components/schemas/NvrStatus' CameraList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Camera' UpdateCameraRequest: type: object additionalProperties: false properties: label: type: string maxLength: 200 enabled: type: boolean PtzCode: type: string enum: - Up - Down - Left - Right - LeftUp - RightUp - LeftDown - RightDown - ZoomTele - ZoomWide - FocusNear - FocusFar - IrisLarge - IrisSmall - GotoPreset - SetPreset - ClearPreset PtzRequest: type: object required: - code - action additionalProperties: false properties: code: $ref: '#/components/schemas/PtzCode' action: type: string enum: - start - stop speed: type: integer minimum: 1 maximum: 8 preset: type: integer minimum: 1 maximum: 255 RecordingsRequest: type: object required: - from - to additionalProperties: false properties: from: type: string format: date-time to: type: string format: date-time RecordingSegment: type: object required: - start - end - type properties: start: type: string format: date-time end: type: string format: date-time type: type: string description: continuous motion: null alarm or manual.: null size_bytes: type: integer format: int64 RecordingsResult: type: object required: - segments description: The result of an sphere.recordings.find job. properties: segments: type: array items: $ref: '#/components/schemas/RecordingSegment' truncated: type: boolean StreamProfile: type: string enum: - main - sub description: main is full resolution; sub the NVR's low-resolution stream (grids). OpenStreamRequest: type: object required: - camera_id additionalProperties: false properties: camera_id: type: string format: uuid kind: type: string enum: - live - playback default: live profile: $ref: '#/components/schemas/StreamProfile' start: type: string format: date-time description: Playback from (playback only). end: type: string format: date-time description: Playback until at most 24 hours after start.: null StreamState: type: string enum: - starting - live - failed - stopped StreamSession: type: object required: - id - camera_id - kind - profile - path - whep_url - token - token_expires_at - state - error properties: id: type: string format: uuid description: The viewer session. camera_id: type: string format: uuid kind: type: string enum: - live - playback profile: type: string path: type: string description: The media path. whep_url: type: string description: Where to play it (WHEP). token: type: string description: Bearer token for whep_url (a few minutes). token_expires_at: type: string format: date-time state: $ref: '#/components/schemas/StreamState' error: type: string start: type: string format: date-time nullable: true end: type: string format: date-time nullable: true Alarm: type: object required: - id - occurred_at - code - action - channel - nvr_id - nvr_name - camera_id - camera_name - data - acked_at - acked_by_email properties: id: type: string format: uuid occurred_at: type: string format: date-time code: type: string description: The NVR's event code (VideoMotion VideoLoss: null AlarmLocal: null …) or NVROnline/NVROffline.: null action: type: string enum: - start - stop - pulse channel: type: integer nvr_id: type: string format: uuid nullable: true nvr_name: type: string camera_id: type: string format: uuid nullable: true camera_name: type: string data: type: object nullable: true additionalProperties: true acked_at: type: string format: date-time nullable: true acked_by_email: type: string AlarmPage: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/Alarm' next_before_at: type: string format: date-time next_before_id: type: string format: uuid AckAlarmsRequest: type: object additionalProperties: false properties: ids: type: array maxItems: 500 items: type: string format: uuid before: type: string format: date-time description: Acknowledge every alarm up to this moment. AckAlarmsResult: type: object required: - acked properties: acked: type: integer SavedView: type: object required: - id - name - layout - cells - shared - created_at - updated_at properties: id: type: string format: uuid name: type: string layout: type: integer enum: - 1 - 4 - 6 - 8 - 9 - 16 - 25 - 36 - 64 cells: type: array description: Camera per cell, in order (null - empty cell). items: type: string format: uuid nullable: true shared: type: boolean created_at: type: string format: date-time updated_at: type: string format: date-time ViewList: type: object required: - items properties: items: type: array items: $ref: '#/components/schemas/SavedView' ViewInput: type: object required: - name - layout - cells additionalProperties: false properties: name: type: string minLength: 1 maxLength: 200 layout: type: integer enum: - 1 - 4 - 6 - 8 - 9 - 16 - 25 - 36 - 64 cells: type: array maxItems: 64 items: type: string format: uuid nullable: true shared: type: boolean default: false description: Visible to the whole tenant (needs views:manage).