Overview
Capabilities
0 of 4 readySigned-in identity
anonymousServer & data source
—Sign in
Session
activeRefresh exchanges the continuation token for fresh tokens; the absolute session expiry set at sign-in never extends. There is no server-side logout in this MVP.
Server
No session
Sign in to unlock the controller list and the live hub. Both authorize with the same bearer token.
Last authentication response
Controllers
Authorized list
0 controllers| ID | Name | Model | State | Area | Clock | Battery | Actions |
|---|
List not loaded
Load the authorized list to see every controller this token grants, with its decoded
state. Off VPN the endpoint answers 503 controllers.data_unavailable
while authentication keeps working.
Last state-refresh response
Live hub
Connection
disconnected
Window
0 ids
Authorized sends no ids at all, so the request stays the same size however many controllers you are authorized for. Ids are rejected under it, not ignored.
Enter accepts a single id, a pasted comma/space separated list, or a controller name from the loaded list. Max 500 distinct positive ids.
Add a key by number instead
Model code is required except wildcard event 6; Elog keys must have none. 1–256 distinct keys. The catalog picker adds both numbers of a boolean parameter; the manual row still takes any number.
No window
Connect and replace the window — the hub answers with an authoritative snapshot at sequence 1.
Projects
Authorized projects
Every project this token's user may work in — favorites first, then by name. The entry marked token project is the one the access token was issued for.
How to use this view.
Read this points the tester at that project. All three reads then carry
the id: the Controllers view sends GET /api/v1/controllers?projectId=…,
its state refresh sends the same id in the body of
POST /api/v1/controllers/states, and the Live hub sends it on
ReplaceControllerWindowAsync. Each carries it where it carries its
other values — a query parameter on the read with no body, a request field on the
two that have one. A surface that omits it does not fail; it quietly reads the
token's own project instead. Switching drops the loaded list, its states and any
open window, because they belong to the project they were read in — reload the
list and re-apply the window after choosing. Read my own stops sending
the id, which is exactly the request every client made before selection existed.
The star calls
PUT /api/v1/auth/projects/{projectId}/favorite, one of the two writes
this API performs against the shared database. It answers 204, so the
list is re-read afterwards and the star reflects what was stored, not what was
sent. Favorites are per user and independent of which project is being read.
Choosing a row also calls
POST /api/v1/auth/projects/{projectId}/entry, the other write, which
raises the stored counter shown as Logins. Nothing on the server
knows when someone opens a project — the project is named per request and no
selection is stored — so the count only moves when a client reports the act. Every
call increments: this view sends it when a row is chosen, never from a read, a
reload or a reconnect. Read my own names no project and counts nothing.
Selecting a project changes nothing about the session and issues no
token. The server resolves the identity to read with from this same list, so a
project that is not in it is refused with
controllers.project_forbidden — never with an empty list.
Service calls
Fault reports
0The fault reports an operator raises against a controller, and the triage a
dispatcher performs on them. Five routes:
GET /api/v1/service-calls (this list),
GET /api/v1/service-calls/{id},
GET /api/v1/service-calls/vocabulary,
POST /api/v1/service-calls and
PUT /api/v1/service-calls/{id}.
The group ships disabled: two of its routes write to the shared
database, so an environment turns ServiceCallsApi on deliberately.
While it is off every route answers 404.
Authorization is the same as everywhere else. The list joins the caller's authorized
controller set in SQL, so a call on a controller this token may not read is never
returned and no refusal says so. An unknown call number and an unreadable one both
answer service_calls.service_call_not_found. Opening a call on a
controller outside that set answers
service_calls.controller_forbidden, whether or not it exists.
The status, fault equipment and technician a form offers come from the vocabulary
route, and a value absent from it is refused with
service_calls.unknown_code. Names are translation keys
(service_status_finished), not display text — a client maps them to its
own language, and this tester shows the key.
Update replaces. The stored procedure overwrites every field it takes, so the edit form sends the current values for everything it is not changing. What a technician recorded doing, and the closing date, are read-only here: this API holds no grant for the procedure that writes them, so a call moved to a finished status still shows no closing date.
Dates are dates. The record keeps a day with no time of day, so the contract
publishes openedOn and closedOn rather than inventing a
midnight timestamp.
Agam Assistant
Ask about this project
Questions are answered from the same data the REST routes return, read as you, in the project named above. The model can list controllers, read their live state, open one controller's full record and read this project's service calls — and it can reach no other project.
It can also prepare a service call: ask it to open one and it fills the form in and shows it to you. Nothing is written until you press Open this call, which sends it under your own token. The assistant holds no write of its own.
You have not chosen a project, so questions are answered about the one this token was issued for. to ask about another.
Answers are produced by a language model from tool results. Treat the
text as its reading of the data; the controller table beside each answer is the
data itself. Available only while the server's AssistantQuery feature
is enabled — otherwise every question answers
404 http.not_found.
Event definitions
Named events
0 eventsCatalog not loaded.
| Model | Event | Off event | Parameter | Reference label | Value kind | Register | Zone | Actions |
|---|
Catalog not loaded
Sign in first — the endpoint requires the same bearer token as the rest of the API — then load the catalog. It is identical for every caller and fixed for the life of the server process.
Command definitions
What each controller model can be told to do, where that writes on each firmware layout, and the evidence behind it — the write-side twin of the state reference. A row marked not encodable is declared so its address is recorded, not offered: its evidence does not settle what it does.
Declared commands
0 declared commandsState definitions
Published state values
0 valuesLoading the published contract...
What the badges on a row mean
Every value states three things about itself. They are three separate facts, not one sentence: a number joined from a register pair whose unit is unconfirmed is a confirmed value with an unnamed unit. Published by the API from the library that owns the tokens, so this list cannot drift from the decoder.
Compound value shapes
A register whose meaning needs more than a number publishes one of these, so no caller has to re-derive a unit, look up a code or unpack a word.
Server logs
Rolling files
0 filesNo files loaded
Load the rolling log files written by the Serilog file sinks. Errors are also written to a
separate recom-backend-errors-*.ndjson stream.
Push
This browser can receive push
checking…- Origin
- Secure context
- Service worker
- Push API
- Notifications
- Permission
- Firebase config
Register this browser with Firebase
no tokenThe registration token stays only in this page's memory. Saving a subscription sends it to the authenticated RECOM API; copying it is what a Firebase Console test message needs.
Choose what to be notified about
not configuredActive subscriptions and delivery
noneNo active subscriptions
Finish steps 1 to 3 and save a subscription; it appears here with its controller and filter counts.
Nothing received yet
Keep this tab active, then send a Firebase Console test message to see its safe summary here.
Firebase Console smoke test — proving delivery without the backend
- Finish step 2 and copy this origin's registration token.
- Open Firebase Console > Messaging, create a notification, then choose Send test message.
- Paste the token and send once with this tab active; the summary appears above.
- Put the tab in the background, send again, and click the system notification to return here.
localhost and the deployed tester use different tokens. This path proves browser permission, the Web App configuration and service-worker receipt on its own; the queued diagnostic send in step 4 is the one that also needs the backend delivery worker and a Firebase sender credential.
Contract
REST
- Sign in
- POST /api/v1/auth/sessions
- Refresh
- POST /api/v1/auth/sessions/refresh
- Identity
- GET /api/v1/auth/me
- List
- GET /api/v1/controllers
- States
- POST /api/v1/controllers/states
- Catalog
- GET /api/v1/controllers/event-catalog
?modelCode=&layout= - Logs
- GET /api/v1/logs · /{fileName}
- Health
- GET /api/v1/healthz/live · /ready
Failures are RFC 9457 application/problem+json with a stable dotted
code, traceId and correlationId — the Activity feed keeps all three.
Realtime
- Hub
- /hubs/v1/controller-states
- Invoke
- ReplaceControllerWindowAsync
- Callbacks
- ControllerWindowResetAsync
ControllerStatesChangedAsync
ControllerEventsReceivedAsync
ControllerResyncRequiredAsync - Protocol
- SignalR JSON over WebSocket
- Errors
- HubException, dotted codes (live.* sql.* redis.*)
Hub failures are plain exception messages, not problem documents. Enum values cross the hub as numbers; REST enums cross as lowercase strings.
Not production-ready
sessionStorage and are cleared
when the tab closes. Do not use this page as an operational UI.
ELOG.Events ingestion and full backpressure instrumentation are not
implemented yet — event batches carry only what the sharded device.change ingestion
produces. The device event catalog names parameters for LT and DM alone; every other model's events
arrive Unclassified with the number and register address intact. Asking the catalog for
one model adds displayLabels — the Agam reference's own UI text, vendored unreviewed and
machine-translated into Latin in places. It is there to be read by a person, never keyed on.