API reference
Overvis HMI has a typed tRPC API. The web app, local workspaces, the hmi CLI, and your own
scripts and integrations all use it, so almost anything you can do in the app, a script can do with
an API key. The procedure inventory is the authoritative list of
supported procedures, with the input, output, access rule, and errors of each.
Start here
Section titled “Start here”- All procedures and types
- Endpoints
- Device API
- Parameters
- Live values
- Readings
- Report presets
- Report comments
- Templates
Authentication
Section titled “Authentication”The web app authenticates with your signed-in session. Other clients send an API key in the
x-api-key header. There are two kinds of key:
- A personal API key acts as you: with your role in every workspace you belong to, and with your role in each billing account. It also reaches your account’s own data: billing accounts, notification settings, Helpdesk tickets, assistant chats, and the security overview of your sign-in methods and signed-in devices. You create one on Account settings → API keys.
- A workspace API key reaches only one workspace, with your role there. The
hmi pullcommand that Develop locally in Workspace settings gives you creates one for the local workspace. It cannot list or create workspaces or reach account data such as assistant chats, notification settings, or Helpdesk tickets. Of billing, it reads only its workspace’s billing status, and which billing account pays for it when you are an admin or owner of that workspace. Account settings → API keys lists your workspace API keys as Local workspace keys, and each workspace’s settings list them on the CLI keys tab.
Neither kind of key signs in to the web app, and a suspended account cannot use either. A key stops working when it is revoked.
A few things work only in the web app with a signed-in session: creating and revoking personal API keys, signing out devices, deleting the account, opening a device’s web interface, linking Telegram, and answering the assistant’s confirmation cards and questions. The procedures behind them are not in the inventory and may change without notice. Procedures in the inventory that accept only a signed-in session say so in their Access entry and refuse both kinds of key.
Transport
Section titled “Transport”The API is tRPC over HTTP. Queries generally retrieve data and mutations perform actions, but live
reads such as parameter.readValues and parameter.readValuesStream are also mutations.
Subscriptions stream updates; the live-read stream is returned by a mutation. The generated
PublicAppRouter declaration in @overvis/hmi-shared/app-router declares each procedure’s kind
and return type for a compatible tRPC client.
Clients may name themselves and their version in the x-hmi-client header as <name>/<version>,
for example cli/0.1.0. The header is optional; requests without it, or from a client name the API
does not track, are accepted. When a tracked client is older than the API supports, every tRPC
procedure and HTTP route answers PRECONDITION_FAILED (HTTP 412) with the code client_outdated
and details: { client, minimum }, where minimum is the oldest version the API accepts.
A tRPC request body may be at most 4 MB. workspace.push accepts up to 32 MB. Procedures that
carry encoded content (report render uploads, problem reports with a screenshot, assistant messages
with images) or bulk rows (readings.insertExact) accept larger bodies up to the limits of their
inputs. A larger body fails with PAYLOAD_TOO_LARGE (HTTP 413), the code request_body_too_large,
and details: { limitBytes }. A push too large for one request is sent in parts of at most 4 MB
each (see Large pushes).
Permissions
Section titled “Permissions”Workspace procedures apply the caller’s membership role:
| Role | Typical API access |
|---|---|
| View only | Read setup metadata and live parameter values; no live writes |
| Operator | View-only access plus live writes, acknowledging and shelving alarms, quick reports, report comments and signatures, and report renders |
| Editor | Operator access plus setup, dashboards, reports, files, and local sync |
| Admin | Editor access plus members, invitations, workspace name, branding, limits, retention, stored-data deletion, and everyone’s workspace API keys |
| Owner | Admin access plus deleting the workspace and moving its billing |
The procedure-specific pages call out stricter requirements. The full capability matrix is in Workspace roles. The server checks every call, also for actions a UI hides.
Values and identifiers
Section titled “Values and identifiers”- Input schemas are closed: an argument name the procedure does not declare is rejected. Configuration maps accept keys according to their declared schema.
- IDs are UUID strings. Input accepts them in any letter case. IDs the server assigns or reads from its records are lowercase; IDs inside stored configuration keep the case they were saved with.
- Dates and times are ISO strings. Setup and dashboard-owned UI display them in the browser
timezone as
YYYY-MM-DD HH:mm:ss. Report widgets use the configured report timezone. - Localized text is either one string or an object whose keys are locale codes.
- Procedures that search by name (
parameter.searchByName,parameter.searchBranchesByName,endpoint.searchByName,dashboard.searchByName) take aqueryof 2–100 characters; a shorter one fails withinput_invalid. They return at most 25matchesand sethasMorewhen there are more. - Optional fields omitted from an update keep their existing value unless the procedure says otherwise.
Errors
Section titled “Errors”tRPC failures include a standard error category in data.code and a machine-readable code in
message. Common categories are:
UNAUTHORIZED: no valid cookie session or API key (plain HTTP routes such as file, blob and image downloads sendauthentication_required);FORBIDDEN: the caller is authenticated but lacks the required workspace role, the workspace is suspended (workspace_suspended, withdetails.reasons:admin,balance, or both), or the account is not admitted (admission_required) or has not finished onboarding in the web app (onboarding_required);NOT_FOUND: the object does not exist or is outside the caller’s accessible workspace;BAD_REQUEST: malformed input, a failed validation rule, or a conflicting object state;CONFLICT: revision or uniqueness conflict that requires fresh data before retrying;PRECONDITION_FAILED: the request needs something first, such as uploading missing blobs (push_blob_missing) or updating an outdated client (client_outdated).
data.code is the error category and the documented message code the domain-specific reason.
Validation failures use message: "input_invalid" and include data.issues with path,
keyword, and optional params. Domain failures may include data.details; when a failure
concerns one input, data.details.field names its input path (for example source.formula). Each
domain code is always sent with the same data.code: @overvis/hmi-shared/api-errors exports
every public code with its status (publicApiErrorStatus, and per domain in
publicApiErrorStatusTables) and the isPublicApiErrorCode guard. Unexpected failures use
message: "internal_error"; stack traces are not part of the response.
The formatter also substitutes internal_error when an error has no public message code, including
default tRPC messages such as UNAUTHORIZED and FORBIDDEN. This fallback preserves data.code;
it does not mean that every such response is an internal server error. data.code identifies
authentication and permission failures. message is a code, not text for display; the app
translates domain codes and validation fields into UI messages.
Concurrent setup edits
Section titled “Concurrent setup edits”Parameters, parameter branches, endpoints, dashboards, report presets, automations, alarm rules,
and edge programs carry a revision that changes whenever the entity does. These procedures take
an optional baseRevision, the revision the caller loaded, and refuse to replace a change made
since then:
parameter.update,parameter.updateBranch,endpoint.update,dashboard.update,automation.update,alarmRule.update,edgeProgram.update,edgeProgram.updateSettings(with the endpoint’srevision),report.preset.update, andreport.preset.saveTemporary(a stale save of a quick report fails instead of returning the report someone else saved);endpoint.delete,endpoint.enable,endpoint.disable,dashboard.delete,dashboard.enable,dashboard.disable,automation.delete,automation.setDisabled,alarmRule.delete,alarmRule.setDisabled,edgeProgram.delete, andreport.preset.delete;parameter.deleteSelection, which takesparamBasesandbranchBases: therevisionof each selected parameter and branch, by id. Entities deleted with a selected branch are not checked.
When the stored entity has another revision, the call changes nothing and fails with CONFLICT,
the code setup_conflict, and details: { entityType, entityId, serverRevision }. A deleted
entity fails with its not-found code (for example parameter_not_found), and so does
parameter.deleteSelection when a selected entity is gone. A call without a base is not checked:
the last write wins. Changes from every source move the revision, including CLI pushes, other
users’ edits, and toggles, so enabling or disabling an entity also changes it.
Workspace changes
Section titled “Workspace changes”workspace.watchChanges({ workspaceId }) is a subscription that tells a client when to refresh
what it shows of one workspace. Every event carries the workspace’s whole current state as the
caller sees it: setupRevision, name, disabled, suspensionReasons, and the caller’s
access. setupRevision moves whenever setup changes: endpoints, parameters, dashboards and
drawings, report presets and comments, automations, alarm rules, edge programs, files, or branding.
Events carry the state, not a list of what changed.
The first event has type: "resync" and reports the state when the subscription starts. Later
events have type: "changed", except after the server may have missed changes, when it sends
another resync. A client that reconnects gets a new first event, so comparing states covers
anything missed in between. The subscription accepts the same callers as workspace.get, including
suspended workspaces and API keys scoped to that workspace. It ends when the caller loses access or
the workspace is deleted. Live connection status of endpoints and device values have their own
subscriptions and are not part of this feed.
Endpoint status and traffic
Section titled “Endpoint status and traffic”Endpoint connection status comes from the live Modbus TCP connection, not from setup. Any workspace member can read it:
endpoint.status({ workspaceId })returns every endpoint of the workspace with itsstatus:state(connecting,connected,disconnected, orerror), the activeremoteaddress,connectedAt, andlastError.statusisnulluntil the first status for that endpoint arrives.endpoint.watchStatus({ workspaceId })is a subscription: onesnapshotevent with the same list, then achangeevent whenever one endpoint’s status changes. A reconnecting subscriber gets a new snapshot.endpoint.traffic({ endpointId, channels? })is a subscription of live Modbus frames of one endpoint:tx(requests queued for sending),sentandrx(Modbus TCP ADUs as hex), andevents(connection changes and request errors). There is no history; frames start when the subscription opens.
When live endpoint status is unavailable, these procedures fail with endpoint_live_unavailable.
Assistant chats
Section titled “Assistant chats”A personal API key can list, create, read, rename (assistant.renameChat), and delete your chats,
send messages (assistant.addToQueue), follow a run (assistant.watchChat,
assistant.runtimeState), continue a run that stopped short, cancel a background job wait, and
stop everything a chat runs (assistant.abortTurn). A run started this way acts as you while the
key is valid and stops when you revoke the key. Workspace API keys cannot reach chats.
When the run waits for you, the API shows what it waits on (assistant.getPendingProposal,
assistant.getPendingQuestion, assistant.getPendingGuard) but cannot answer it: confirmation
cards, the assistant’s questions, and the repeated-step and spending-cap prompts are answered only
in the app. The run stays paused until you answer there or stop it with assistant.abortTurn. A
message sent while the assistant is replying is queued and goes out when the reply completes.
Message input
Section titled “Message input”assistant.addToQueue takes a message id you generate (a UUID; a repeat with the same id returns
what became of the first call), the chatId, stopGeneration, and the message itself.
identityis required on every chat command:{ userId, sessionId }, with your own user id andsessionId: null. Any other value is refused withassistant_identity_changed. No procedure returns your user id directly: it is theuserIdof theownerinbilling.members.listfor the billing account thatbilling.accounts.listmarkspersonal: true.stopGenerationis the chat’s Stop count, asassistant.listChatsandassistant.runtimeStatereturn it. A value older than the chat’s last Stop stores the message asheld, waiting for Send, which only the app offers. A held message is not sent through the API; a new message with the current count is.message.levelis the assistant level the run uses; the chat’s current one islevelinassistant.listChats.message.contentis the text.message.contexttells the assistant where the user is:pathname(an app path, or/),locale(the UI language code), andworkspaceId,workspaceName,dashboardId,dashboardName,endpointId,endpointName,parameterId, andparameterName, each set to the item the message is about ornull.timeZone(an IANA name) is optional. The server stampsnow, and the input does not carry it. Messages queued together must name the same workspace.
Workspace file content
Section titled “Workspace file content”workspace.push and workspace.pull never carry file bytes. They name each mirrored or built file
by { sha256, size }, and the content travels as a content-addressed blob over plain HTTP with the
same authentication:
PUT /api/workspaces/<workspaceId>/blobs/<sha256>stores the raw request body (any content type, at most 50 MB) for a later push to that workspace. The body must hash to<sha256>. Repeating an upload is harmless. Replies204. Uploaded content stays available to pushes for 24 hours. Requires editor, admin or owner access.GET /api/workspaces/<workspaceId>/blobs/<sha256>returns the raw bytes of content that one of the workspace’s files references. Requires editor, admin or owner access.
workspace.missingBlobs({ workspaceId, hashes }) takes up to 100 hashes and returns those the
workspace cannot reference, which are the ones a push needs uploaded. Content the workspace already
references needs no upload; content uploaded to another workspace does not count. A push that
references content the workspace lacks fails with push_blob_missing; details.blobs lists the
hashes it lacks. Blob errors are blob_invalid_hash, blob_hash_mismatch, blob_too_large
(status 413), and blob_not_found, sent as { "message": "<code>" }.
Large pushes
Section titled “Large pushes”A push whose changes do not fit one request can be staged in parts and applied as one push.
- Parts. Each part is a push input of its own. Together the parts hold every change exactly once: each upsert, tombstone, file, and built file in one part, with its base in the same part.
workspace.pushStagetakes one part with itspartnumber, counted from0, thepushId, and anattemptUUID that identifies one staging attempt. Staging a part again replaces it. Staging a part of another attempt discards the parts of earlier attempts of the push.workspace.pushCommit({ workspaceId, pushId, attempt, parts })applies that attempt’s parts as one push, with the same checks, conflicts, and result asworkspace.push, then discards them. Parts of different attempts are never combined.- Limits. The parts of one push may hold at most 64 MB of JSON together
(
push_stage_too_large). A commit fails withpush_stage_incompletewhen a part is missing (details.partslists them), and withpush_stage_overlapwhen two parts hold the same change. A user stages one push per workspace at a time: staging a part of another push discards the earlier one. Parts not committed within a day are discarded. - Identity. A staged push has the same identity as the same push sent at once, however it is
split. A push sent again under the same
pushId, at once or staged in parts under a newattempt, returns the first result when the push was already applied.
Compatibility
Section titled “Compatibility”The TypeScript router declaration is generated from the running server contract. The
procedure inventory matches the @overvis/hmi-shared version of the
same release.