Session protocol (v0)¶
The tomix session protocol lets other programs work on a model that tomix holds
open: editors, the tomix UI, and AI agents through tx mcp. Every client
attached to a session sees the same model, the same undo history and the same
change events as tx interactive.
This page is the v0 specification. tx serve implements it on stdin and
stdout (tx serve).
A localhost WebSocket carries the same messages to several clients of one
session; tx ui opens it. The design behind it is
ADR 0001.
tx serve does not serve query.run or $/progress yet. initialize lists what it does serve, so a client can check
capabilities rather than this page.
Transport¶
Messages are JSON-RPC 2.0 on either transport.
stdio¶
- JSON-RPC over the server's stdin and stdout. stderr carries logs only and is never part of the protocol.
- Each message is framed like the
Language Server Protocol:
a
Content-Lengthheader giving the body's length in bytes, a blank line, then the UTF-8 JSON body.
- Batch requests (a JSON array of messages) are not supported in v0 and are
answered with error
-32600. - The client may send several requests without waiting for answers. The server answers each one; answers can arrive in a different order from the requests.
WebSocket¶
A session shared by several clients (the browser UI, agents, other tools)
listens on ws://127.0.0.1:<port>/ws.
- One JSON-RPC message per WebSocket text message, with no
Content-Lengthheader. Binary messages are answered with error-32600. - Each connection is one client, with its own
initialize,clientId, requests and transaction. Every client receives every session event. - The server listens on
127.0.0.1only. Every request must: - carry the session token, as
Authorization: Bearer <token>or, from a browser, which cannot set headers on a WebSocket, as?token=<token>; - name the server in
Host(127.0.0.1:<port>orlocalhost:<port>); - send no
Origin, or the server's own (http://127.0.0.1:<port>).
A request without the token is answered with HTTP 401 and
TOMIX_UI_UNAUTHORIZED; one naming another host or origin with 403 and
TOMIX_UI_FORBIDDEN.
Status endpoint¶
GET http://127.0.0.1:<port>/status answers a small JSON object for tools
that poll, such as a status bar. It needs the token like every other request.
It reads counters the session already keeps and never waits for the model, so
polling it every second is fine. Fields may be added; none are renamed or
removed within a protocol version.
{
"protocolVersion": "0",
"model": "C:/models/Sales",
"state": "dirty",
"dirty": true,
"version": 42,
"undoSteps": 7,
"redoSteps": 0,
"canReload": true,
"sourceUnavailable": false,
"transaction": { "id": "t18", "client": "mcp-1", "label": "rename measures" },
"clients": ["tomix-ui-1", "mcp-1"],
"lastChange": { "version": 42, "client": "mcp-1", "at": "2026-10-05T14:03:11Z" }
}
stateis a session state (clean,dirty, …), orclosedwhen no model is open.canReloadisfalsefor a model on a server or in Power BI Desktop, which the session cannot read again;stalethen ends only with a forced save.sourceUnavailableistruewhen the session's last check could not reach its server, for example because Power BI Desktop has closed. Saving there fails;session.savewithoutputFilestill writes the model to files.transactionis the open explicit transaction, ornull.clientslists the connected clients that have sentinitialize.lastChangeis the lastmodel.changedbatch since the model was opened, ornull.
Discovery¶
tx ui writes one file per open model to ~/.tomix/live/ (only the current
user can read it):
{
"model": "C:/models/Sales",
"pid": 18244,
"port": 51873,
"url": "http://127.0.0.1:51873/",
"token": "q3V…",
"startedAt": "2026-10-05T14:00:02Z"
}
The file is removed when the process ends. A file whose pid is no longer
running is stale: ignore it (tx deletes such files when it finds them). No
file, or a refused connection, means no session is open.
tx serve <model> reads this file: when tx ui holds the model, it joins that
session over the WebSocket instead of opening a second one.
The page¶
GET http://127.0.0.1:<port>/?token=<token> serves the session's browser page,
the URL tx ui prints. Its script and style run under a per-response
Content-Security-Policy nonce, it sends no referrer, and it connects to /ws
like any other client (clientInfo.name tomix-ui).
Lifecycle¶
- The client sends
initialize. Until it has an answer, any other request fails with-32002(not initialized). - The client works with the session: requests, and notifications from the server.
- The client sends
shutdown, waits for its answer, then sends theexitnotification. The server exits with code 0 after ashutdown, and 1 ifexitarrives without one.
A client that disconnects without shutdown detaches. It never closes the
session (only the host does), and an explicit transaction it left open is
rolled back; the other clients receive transaction.closed with outcome
rolledBack.
Conventions¶
Method names¶
Methods are named area.verb: session.open, object.set, model.changed.
Names starting with $/ belong to the protocol itself ($/cancelRequest,
$/progress). A server ignores a $/ notification it does not know, and
answers an unknown $/ request with -32601.
Requests and results¶
- Parameters use the same camelCase names as the matching CLI command's JSON.
object.settakes thepathand the property assignmentstx settakes, for example. The model is always the session's, so no request names one, and the--save,--stageand--revertchoices of the CLI do not exist: every edit applies to the session, andsession.savepersists it. - A result is the same envelope the CLI writes with
--output-format json, plus the session version it reflects:
data has exactly the shape of the command's data, so a client can share
code between the CLI and the protocol. diagnostics holds warnings.
version is the session version after the request; a read reports the
version it read.
Addressing objects¶
Every object in a session has an ID, an opaque string such as o1k3 that stays
the same through renames and moves and is never reused within the session.
Requests that take an object accept either path or id, never both:
IDs do not survive the session: store paths (or lineage tags), not IDs.
Transactions¶
Each request runs as one transaction: it applies completely or not at all, and
it is one undo step. transaction.begin groups the client's following
requests into one step until transaction.commit or transaction.rollback.
While a client's transaction is open, other clients' edits wait for it, and
their reads see the last committed version.
Errors¶
A failed request is answered with a JSON-RPC error. Errors from tomix carry the same code, message and hint as the CLI's error output:
{
"jsonrpc": "2.0",
"id": 30,
"error": {
"code": -32000,
"message": "Object not found: Sales/Profit",
"data": {
"code": "TOMIX_OBJECT_NOT_FOUND",
"hint": "Run 'tx get' to list the tables, or 'tx get \"Sa*\"' to filter.",
"exitCode": 2
}
}
}
code |
Meaning |
|---|---|
-32700 |
The body is not valid JSON. |
-32600 |
The message is not a valid JSON-RPC request (including batches). |
-32601 |
Unknown method. |
-32602 |
The parameters do not match the method. data.code names the problem when tomix can, for example TOMIX_INVALID_TYPE. |
-32603 |
Internal error (data.code is TOMIX_UNEXPECTED). |
-32002 |
A request arrived before initialize. |
-32800 |
The request was cancelled with $/cancelRequest. |
-32000 |
Any other tomix error: data.code is its TOMIX_* code, data.hint the remediation and data.exitCode the exit code the CLI would use. |
Clients should branch on data.code, not on message.
Cancellation and progress¶
$/cancelRequest(notification,{ "id": <request id> }) cancels a request. One not yet started is dropped; an edit in progress rolls back; a query stops. The cancelled request is answered with error-32800.- A request whose params include
"progressToken"gets$/progressnotifications with that token while it runs (a refresh, a query, a BPA run).
Versioning¶
- The protocol version is
"0".initializereturns it, and the client sends the version it speaks. - Additive changes stay in v0: new methods, new notifications, new optional
parameters, new result or event fields, new
TOMIX_*codes. Clients must ignore fields and notifications they do not know, and should checkcapabilities.methodsbefore calling a method added later. - Anything else (removing or renaming a method, field or value, changing a type or a default) is a new protocol version.
Methods¶
| Method | Kind | Purpose |
|---|---|---|
initialize |
request | Agree on the protocol version; learn the server's capabilities. |
shutdown |
request | Detach; the next message is exit. |
exit |
notification | End the connection. |
session.open |
request | Open a model as the session. |
session.close |
request | Close the session, saving or discarding unsaved changes. |
session.status |
request | Model, state, unsaved changes, undo depth, open transaction. |
session.save |
request | Write the session to its source, or a copy elsewhere. |
session.snapshot |
request | The whole model at the current version, to (re)synchronize. |
session.history |
request | The steps undo and redo walk through. |
session.undo |
request | Revert the last step. |
session.redo |
request | Reapply the last undone step. |
session.reload |
request | Read the model's files again after they changed outside the session. |
transaction.begin |
request | Group the following requests into one undo step. |
transaction.commit |
request | Keep the group as one step. |
transaction.rollback |
request | Discard the group. |
model.summary |
request | Model name, format and object counts. |
model.tree |
request | One level of the object tree, for lazy loading. |
object.get |
request | One object's properties, or a listing. |
object.find |
request | Search names and expressions. |
deps.get |
request | Upstream and downstream dependencies of an object. |
object.add |
request | Create an object. |
object.set |
request | Change properties. |
object.remove |
request | Remove an object. |
object.move |
request | Rename or move an object. |
model.replace |
request | Find and replace across the model. |
bpa.run |
request | Run the Best Practice Analyzer. |
bpa.fix |
request | Apply the analyzer's fixes. |
dax.format |
request | Format an expression, or an object's. |
dax.check |
request | Check the model's DAX and references. |
query.run |
request | Run a DAX or DMV query (server-backed models). |
command.run |
request | Run a tx command in the session, as tx does for a model a tx ui holds. |
$/cancelRequest |
notification | Cancel a request. |
$/progress |
notification | Progress of a long request. |
model.changed |
notification | A transaction was committed. |
session.state |
notification | The session's state changed. |
session.saved |
notification | The session was saved. |
transaction.opened |
notification | A client opened a transaction. |
transaction.closed |
notification | That transaction was committed or rolled back. |
diagnostics.updated |
notification | Dependency, DAX and BPA results were recomputed. |
Reserved for later versions: session.merge (#374).
Lifecycle methods¶
initialize¶
clientInfo names the client in change events (origin.client); the server
returns the ID it will use for this client: the name, numbered per name in the
session (tomix-ui-1, tomix-ui-2).
capabilities.diagnostics asks the host to keep dependencies and DAX
diagnostics up to date after every change and to send this client
diagnostics.updated: true, or { "bpa": true } to include BPA findings.
Leave it out, or send false, for neither.
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "0",
"clientInfo": { "name": "tomix-ui", "version": "0.1.0" },
"capabilities": { "diagnostics": { "bpa": true } }
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "0",
"serverInfo": { "name": "tx", "version": "0.8.1" },
"clientId": "tomix-ui-1",
"capabilities": {
"methods": ["session.open", "session.close", "session.status", "object.get", "object.set", "session.undo"],
"notifications": ["model.changed", "session.state", "session.saved", "diagnostics.updated"]
}
}
}
A server that does not speak the requested version answers with error
-32000 and data.code TOMIX_PROTOCOL_VERSION, listing the versions it
speaks in data.supported.
shutdown¶
exit¶
Session methods¶
session.open¶
Opens a model, in the same forms tx interactive and tx connect take:
model is a TMDL folder or .bim file, or server and database name a
remote model. A session already open is closed first; when it has unsaved
changes the request fails with TOMIX_SESSION_DIRTY unless discard is
true, and while other clients are connected it fails with
TOMIX_SESSION_IN_USE. The result is the new session's status.
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"data": {
"model": "/models/sales",
"source": "/models/sales",
"state": "clean",
"dirty": false,
"version": 0,
"undoSteps": 0,
"redoSteps": 0,
"transaction": null
},
"diagnostics": [],
"version": 0
}
}
session.close¶
Closes the session. With unsaved changes it fails with TOMIX_SESSION_DIRTY
unless save or discard is true. While other clients are connected it
fails with TOMIX_SESSION_IN_USE: the session is theirs too.
{ "jsonrpc": "2.0", "id": 3, "result": { "data": { "closed": true, "saved": true }, "diagnostics": [], "version": 6 } }
session.status¶
The status payload of tx interactive.
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"data": {
"model": "/models/sales",
"source": "/models/sales",
"state": "dirty",
"dirty": true,
"version": 6,
"undoSteps": 4,
"redoSteps": 0,
"transaction": null,
"canReload": true,
"sourceUnavailable": false
},
"diagnostics": [],
"version": 6
}
}
session.save¶
The tx save payload. Without outputFile it writes back to the source and
makes the current version the save point; with it, it writes a copy and the
session keeps saving to its source. serialization and fixBpa work as in
tx save.
When the model's files changed outside the session since it opened, reloaded
or last saved (the session is stale), a save to the source fails with
TOMIX_SESSION_STALE and writes nothing. Pass "force": true to keep the
session's version and overwrite the files, or call session.reload to take
theirs.
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"data": {
"format": "tmdl",
"status": "saved",
"saved": true,
"savedTo": "/models/sales",
"persistence": "file",
"sync": { "status": "notConfigured" }
},
"diagnostics": [],
"version": 6
}
}
session.snapshot¶
Every object with its ID and path, at the current version. A client calls it
after connecting, and again whenever it sees a gap in model.changed
versions.
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"data": {
"name": "sales",
"objects": [
{ "id": "o1", "type": "Table", "path": "Sales", "name": "Sales" },
{ "id": "o2", "type": "Column", "path": "Sales/Amount", "name": "Amount" },
{ "id": "o3", "type": "Measure", "path": "Sales/Total Sales", "name": "Total Sales" }
]
},
"diagnostics": [],
"version": 6
}
}
session.history¶
The history payload of tx interactive: undo steps oldest first, then the
steps redo would reapply, next one first. label is what made the step: the
command line in tx interactive, or the method name for protocol clients.
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"data": {
"steps": [
{ "transaction": "t1", "label": "object.add", "changes": 1, "undone": false },
{ "transaction": "t2", "label": "object.set", "changes": 1, "undone": true }
]
},
"diagnostics": [],
"version": 3
}
}
session.undo¶
Reverts the last step, whoever made it. Fails with
TOMIX_SESSION_NOTHING_TO_UNDO when there is none, and with
TOMIX_SESSION_IN_TRANSACTION while the client has a transaction open.
changes lists what the undo changed, as in model.changed.
{
"jsonrpc": "2.0",
"id": 8,
"result": {
"data": {
"action": "undo",
"transaction": "t4",
"label": "object.remove",
"changes": [
{ "id": "ol", "objectKind": "Measure", "change": "added", "path": "Sales/Avg Sale" }
],
"version": 5,
"dirty": true
},
"diagnostics": [],
"version": 5
}
}
session.redo¶
Reapplies the last undone step. Fails with TOMIX_SESSION_NOTHING_TO_REDO
when there is none.
{
"jsonrpc": "2.0",
"id": 9,
"result": {
"data": {
"action": "redo",
"transaction": "t4",
"label": "object.remove",
"changes": [
{ "id": "ol", "objectKind": "Measure", "change": "removed", "path": "Sales/Avg Sale" }
],
"version": 6,
"dirty": true
},
"diagnostics": [],
"version": 6
}
}
session.reload¶
Reads the model's files again, after they changed outside the session (a
git checkout, another editor). It discards unsaved changes and clears undo
history, so on a dirty session it fails with TOMIX_SESSION_DIRTY unless
discard is true. Objects that keep their path keep their id. Every client
gets one model.changed with origin.kind reload, naming what differs from
the model before; modified objects carry no properties. Fails with
TOMIX_MODEL_LOAD_FAILED, leaving the session as it was, when the files no
longer load, and with TOMIX_SESSION_SOURCE_UNSUPPORTED on a server-backed
session.
{
"jsonrpc": "2.0",
"id": 32,
"result": {
"data": {
"action": "reload",
"transaction": "t9",
"label": null,
"changes": [
{ "id": "oh", "objectKind": "Measure", "change": "modified", "path": "Sales/Total Sales" }
],
"version": 7,
"dirty": false
},
"diagnostics": [],
"version": 7
}
}
transaction.begin¶
Opens a transaction for this client; label names it in history. Fails with
TOMIX_SESSION_TRANSACTION_OPEN when the client already has one. A
transaction idle for 15 minutes is rolled back.
{ "jsonrpc": "2.0", "id": 10, "method": "transaction.begin", "params": { "label": "Margin measures" } }
{
"jsonrpc": "2.0",
"id": 10,
"result": {
"data": { "action": "begin", "transaction": "t7", "label": "Margin measures", "changes": [], "version": 6, "dirty": true },
"diagnostics": [],
"version": 6
}
}
transaction.commit¶
Keeps the transaction's requests as one undo step and publishes one
model.changed. Fails with TOMIX_SESSION_NO_TRANSACTION when none is open,
and with TOMIX_SESSION_TRANSACTION_ENDED when the session already rolled it
back.
{
"jsonrpc": "2.0",
"id": 13,
"result": {
"data": {
"action": "commit",
"transaction": "t7",
"label": "Margin measures",
"changes": [
{ "id": "o2a", "objectKind": "Measure", "change": "added", "path": "Sales/Margin" },
{ "id": "o2b", "objectKind": "Measure", "change": "added", "path": "Sales/Margin %" }
],
"version": 7,
"dirty": true
},
"diagnostics": [],
"version": 7
}
}
transaction.rollback¶
Discards everything the transaction did.
{
"jsonrpc": "2.0",
"id": 14,
"result": {
"data": { "action": "rollback", "transaction": "t8", "label": null, "changes": [], "version": 7, "dirty": true },
"diagnostics": [],
"version": 7
}
}
Reading the model¶
model.summary¶
The tx summary payload.
{
"jsonrpc": "2.0",
"id": 15,
"result": {
"data": {
"name": "sales",
"source": "/models/sales",
"format": "tmdl",
"compatibilityLevel": 1601,
"culture": "en-US",
"defaultMode": "Import",
"counts": {
"tables": 3, "columns": 12, "measures": 4, "relationships": 2, "roles": 0,
"partitions": 3, "calculationGroups": 0, "perspectives": 0, "cultures": 0
}
},
"diagnostics": [],
"version": 6
}
}
model.tree¶
One level of the object tree, for a tree view that loads children on demand.
Without path or id it returns the model's top level (tables, relationships,
roles, ...). hasChildren says whether to show an expander.
{
"jsonrpc": "2.0",
"id": 16,
"result": {
"data": {
"parent": { "id": "o1", "path": "Sales" },
"children": [
{ "id": "o2", "type": "Column", "path": "Sales/Amount", "name": "Amount", "hasChildren": false },
{ "id": "o3", "type": "Measure", "path": "Sales/Total Sales", "name": "Total Sales", "hasChildren": false },
{ "id": "o9", "type": "Partition", "path": "Sales/Partitions/Sales", "name": "Sales", "hasChildren": false }
]
},
"diagnostics": [],
"version": 6
}
}
object.get¶
The tx get payload, with the same parameters: one object (path or id), or
a listing with ls, where and type. Results add each object's id.
{
"jsonrpc": "2.0",
"id": 17,
"result": {
"data": {
"id": "o3",
"type": "Measure",
"path": "Sales/Total Sales",
"properties": {
"name": "Total Sales",
"description": "",
"isHidden": false,
"expression": "SUM ( Sales[Amount] )",
"formatString": "",
"displayFolder": "",
"lineageTag": ""
}
},
"diagnostics": [],
"version": 6
}
}
object.find¶
The tx find payload.
{
"jsonrpc": "2.0",
"id": 18,
"result": {
"data": {
"pattern": "Amount",
"matchCount": 2,
"matches": [
{ "objectPath": "Sales/Amount", "objectType": "Column", "property": "Name", "matchedText": "Amount", "line": 1, "position": 1 },
{ "objectPath": "Sales/Total Sales", "objectType": "Measure", "property": "Expression", "matchedText": "Amount", "line": 1, "position": 13 }
]
},
"diagnostics": [],
"version": 6
}
}
deps.get¶
The tx deps payload. direction is upstream, downstream or both (the
default); unused lists objects nothing depends on instead.
deps.get, dax.check and bpa.run read the last committed version, so they
do not wait for another client's transaction; inside the client's own
transaction they read its uncommitted changes. version is the version they
read.
{
"jsonrpc": "2.0",
"id": 19,
"result": {
"data": {
"path": "Sales/Total Sales",
"objectType": "Measure",
"upstream": [
{ "objectName": "'Sales'[Amount]", "objectType": "Column", "path": "Sales/Amount" }
],
"downstream": []
},
"diagnostics": [],
"version": 6
}
}
Editing the model¶
Every edit applies to the session at once, as in tx interactive, so status
is applied. A rename rewrites the DAX that refers to the object unless
fixRefs is false, exactly as tx set and tx mv do.
object.add¶
The tx add parameters: path, type, expression and set (property
assignments).
{
"jsonrpc": "2.0",
"id": 20,
"method": "object.add",
"params": { "path": "Sales/Margin", "type": "Measure", "expression": "[Total Sales] * 0.1" }
}
{
"jsonrpc": "2.0",
"id": 20,
"result": {
"data": { "added": "Sales/Margin", "status": "applied", "saved": false, "sync": { "status": "notAttempted" } },
"diagnostics": [],
"version": 7
}
}
object.set¶
set maps property names (as tx get prints them) to values.
{
"jsonrpc": "2.0",
"id": 21,
"method": "object.set",
"params": { "path": "Sales/Margin", "set": { "formatString": "0.0%" } }
}
{
"jsonrpc": "2.0",
"id": 21,
"result": {
"data": {
"set": "Sales/Margin",
"property": "formatString",
"value": "0.0%",
"validationErrors": 0,
"status": "applied",
"saved": false,
"sync": { "status": "notAttempted" }
},
"diagnostics": [],
"version": 8
}
}
object.remove¶
Fails with TOMIX_RM_BREAKS_REFS when other objects refer to it, unless
force is true.
{
"jsonrpc": "2.0",
"id": 22,
"result": {
"data": { "removed": "Sales/Avg Sale", "status": "applied", "saved": false, "sync": { "status": "notAttempted" } },
"diagnostics": [],
"version": 9
}
}
object.move¶
Renames or moves path (or id) to to. The object keeps its ID.
{
"jsonrpc": "2.0",
"id": 23,
"method": "object.move",
"params": { "path": "Sales/Margin", "to": "Sales/Margin %" }
}
{
"jsonrpc": "2.0",
"id": 23,
"result": {
"data": { "moved": "Sales/Margin", "to": "Sales/Margin %", "status": "applied", "saved": false, "sync": { "status": "notAttempted" } },
"diagnostics": [],
"version": 10
}
}
model.replace¶
The tx replace parameters (pattern, replacement, regex,
caseSensitive, in, type). All matches change in one transaction.
{
"jsonrpc": "2.0",
"id": 24,
"method": "model.replace",
"params": { "pattern": "Amount", "replacement": "Amt" }
}
{
"jsonrpc": "2.0",
"id": 24,
"result": {
"data": {
"pattern": "Amount",
"replacement": "Amt",
"changeCount": 1,
"previews": [
{ "objectPath": "Sales/Total Sales", "property": "Expression", "before": "SUM ( Sales[Amount] )", "after": "SUM ( Sales[Amt] )" }
],
"status": "applied",
"saved": false,
"sync": { "status": "notAttempted" }
},
"diagnostics": [],
"version": 11
}
}
Analysis¶
bpa.run¶
The tx bpa run payload, without fixes. rules adds rule files and rule
limits the run to some rule IDs, as on the CLI.
{
"jsonrpc": "2.0",
"id": 25,
"result": {
"data": {
"rulesEvaluated": 26,
"violations": 1,
"remaining": 1,
"ruleErrors": 0,
"fixesApplied": 0,
"results": [
{
"ruleId": "MODEL_SHOULD_HAVE_A_DATE_TABLE",
"ruleName": "[Performance] Model should have a date table",
"category": "Performance",
"severity": 2,
"severityLabel": "Warning",
"objectName": "Model",
"objectType": "Model",
"objectPath": "Model",
"description": "Generally speaking, models should generally have a date table.",
"canFix": false
}
],
"status": "unchanged",
"saved": false
},
"diagnostics": [],
"version": 11
}
}
bpa.fix¶
bpa.run with fixes applied to the session, as one transaction.
allowDelete permits fixes that remove objects.
{
"jsonrpc": "2.0",
"id": 26,
"result": {
"data": {
"rulesEvaluated": 26,
"violations": 3,
"remaining": 1,
"ruleErrors": 0,
"fixesApplied": 2,
"results": [],
"status": "applied",
"saved": false
},
"diagnostics": [],
"version": 12
}
}
dax.format¶
The tx format payload. With expression it formats that text and changes
nothing; with path (or id) it formats the object's expression in the
session; with neither, every expression in the model. lang is dax
(default) or m.
{
"jsonrpc": "2.0",
"id": 27,
"result": {
"data": {
"success": true,
"path": "Sales/Total Sales",
"language": "dax",
"formatStatus": "unchanged",
"formatted": "SUM ( Sales[Amount] )",
"status": "unchanged",
"saved": false
},
"diagnostics": [],
"version": 12
}
}
dax.check¶
The tx validate payload: DAX syntax and broken references across the model.
For a client that asked for capabilities.diagnostics, the host also runs it
after every change and announces new results with diagnostics.updated;
dax.check without parameters then answers with those results.
{
"jsonrpc": "2.0",
"id": 28,
"result": {
"data": { "modelName": "sales", "valid": true, "durationMs": 191, "errors": [], "warnings": [] },
"diagnostics": [],
"version": 12
}
}
query.run¶
The tx query payload, for server-backed sessions. maxRows caps the rows
returned (truncated says whether it did). Cancel it with $/cancelRequest.
{
"jsonrpc": "2.0",
"id": 29,
"method": "query.run",
"params": { "query": "EVALUATE TOPN(2, Sales)", "maxRows": 1000, "progressToken": "q-1" }
}
{
"jsonrpc": "2.0",
"id": 29,
"result": {
"data": {
"server": "powerbi://api.powerbi.com/v1.0/myorg/Finance",
"database": "Sales",
"columns": [
{ "name": "Sales[Amount]", "type": "decimal" },
{ "name": "Sales[Date]", "type": "dateTime" }
],
"rows": [
[120.5, "2024-01-03T00:00:00"],
[80, "2024-01-04T00:00:00"]
],
"rowCount": 2,
"truncated": false,
"durationMs": 231
},
"diagnostics": [],
"version": 12
}
}
command.run¶
How a one-shot tx command runs on a model that a tx ui holds: rather than
open the files, which the session would later overwrite, tx connects as a
client named after the command (tx set), sends its command line here and
prints the output (#400). The command runs as in tx interactive: an edit is
one undo step and stays unsaved until a save, and save writes the session.
args is the command line after tx, with local paths absolute, since the
session runs in its own folder. Only add, bpa run, deps, find,
format, get, ls, mv, replace, rm, save, set, summary and
validate run; anything else is invalid params. Nothing can prompt, so a
command that would ask fails instead, as with --non-interactive. stdin is
what the command reads for a - value. colorSystem (NoColors, Legacy,
Standard, EightBit or TrueColor; default NoColors) and width shape
the text output for the caller's terminal.
The result carries the command's exit code and what it wrote to stdout and stderr, whatever the exit code; a command that fails is not a JSON-RPC error.
{
"jsonrpc": "2.0",
"id": 31,
"method": "command.run",
"params": {
"args": ["set", "Sales/Revenue", "--set", "FormatString=#,0", "--model", "/models/sales"],
"colorSystem": "TrueColor",
"width": 120
}
}
{
"jsonrpc": "2.0",
"id": 31,
"result": {
"data": {
"exitCode": 0,
"stdout": "Set: Sales/Revenue.FormatString\n",
"stderr": ""
},
"diagnostics": [],
"version": 13
}
}
Protocol notifications¶
$/cancelRequest¶
$/progress¶
message is what the CLI's spinner or progress bar would show; percentage
is present when the work is measurable.
{
"jsonrpc": "2.0",
"method": "$/progress",
"params": { "token": "bpa-1", "message": "Evaluating rules", "percentage": 40 }
}
Session events¶
The server sends these to every attached client, including the one whose
request caused them. Events name what changed but never carry values: fetch
values with object.get at the event's version.
model.changed¶
One per committed transaction, whether it touched one object or four hundred.
version goes up by exactly one per event; a client that sees a gap calls
session.snapshot. change is added, removed, modified, renamed or
moved; origin.kind is apply, undo, redo or reload.
{
"jsonrpc": "2.0",
"method": "model.changed",
"params": {
"version": 42,
"transaction": "t17",
"origin": { "client": "mcp-1", "kind": "apply" },
"changes": [
{ "id": "o1k3", "objectKind": "Measure", "change": "renamed", "path": "Sales/Sales Amount", "oldPath": "Sales/Total Sales" },
{ "id": "o1k9", "objectKind": "Measure", "change": "modified", "path": "Sales/Margin %", "properties": ["expression"] }
]
}
}
session.state¶
state is clean, dirty, saving, stale or closed. A client shows
unsaved changes from it, and offers reload when the session is stale.
A session on a TMDL folder or .bim file watches its files. When their
content differs from what the session last opened, reloaded or saved, it turns
stale (it may also have unsaved changes; session.status reports dirty
separately), and back again if the files return to that content. stale ends
with session.reload or a session.save with force.
A session on a server or Power BI Desktop asks the server whether any object
in the model changed (every 10 seconds for Power BI Desktop, 30 for a remote
server), and turns stale when something else changed it
(including a refresh it did not run). It cannot reload: session.status
reports canReload: false, and stale ends with a session.save with
force. When the server cannot be reached (Power BI Desktop closed), requests
that need it fail with TOMIX_SESSION_SOURCE_UNAVAILABLE, and
session.status reports sourceUnavailable: true from the next check;
session.save with outputFile still writes the model to files.
{
"jsonrpc": "2.0",
"method": "session.state",
"params": { "previous": "clean", "state": "dirty", "version": 43 }
}
session.saved¶
{
"jsonrpc": "2.0",
"method": "session.saved",
"params": { "version": 43, "savedTo": "/models/sales", "persistence": "file" }
}
transaction.opened¶
{
"jsonrpc": "2.0",
"method": "transaction.opened",
"params": { "transaction": "t18", "client": "mcp-1", "label": "Margin measures" }
}
transaction.closed¶
outcome is committed, rolledBack or timedOut. A commit is followed by
its model.changed.
{
"jsonrpc": "2.0",
"method": "transaction.closed",
"params": { "transaction": "t18", "client": "mcp-1", "outcome": "committed" }
}
diagnostics.updated¶
The host recomputed dependencies, DAX diagnostics and, when asked, BPA findings
for version, 250 ms after the last change. A burst of changes causes one
recompute. Only clients that sent capabilities.diagnostics in initialize
get it. Fetch the results with dax.check, bpa.run or deps.get: without
parameters, dax.check and bpa.run answer with the recomputed results for the
current version instead of running again. BPA results follow the rule files as
they were at that recompute; pass any parameter to bpa.run to run it again.