Commands
A command is POST {Path}/command, with the token, and the JSON body { type, payload }:
{
"type": "query",
"payload": {
"query": "IProduct.Where(n => n.Price < @P0).OrderBy(n => n.Name).Page(0, 20)",
"parameters": [{ "name": "@P0", "type": "decimal", "value": 500 }],
"context": { "culture": "en-US" }
}
}It answers { "kind": "nodes", "items": [ … ], "totalCount": 42, "pageIndex": 0, "pageSize": 20, … }.
Pass values as typed parameters, never as literals. The query language has no literals for
decimals or dates, and n.Price > 100 against a decimal property compares with 0. A parameter is
{ name, type, value }. Its type is string, int, long, double, float, decimal, bool,
datetime, datetimeoffset, timespan, guid or geo, or an array such as guid[]. The SDK's
query builder does this for you, and its toRequest() shows the text it writes
(chapter 19).
Types, properties and relation ends are named by CodeName or by id. CodeNames are case-sensitive.
Reading and querying
| Command | Payload | Answers |
|---|---|---|
whoami |
{ userId, anonymous, name, email, groups, admin } |
|
schema |
the node types with their properties, and the cultures | |
query |
{ query, parameters } |
the result, with its kind (Nodes and values on the wire) |
get |
{ ids } |
{ items }: the nodes the user can read, in the order asked for |
exists |
{ id } |
whether the node exists and the user can read it |
count |
{ type? } |
how many nodes of the type and its subtypes the user can read |
get-related |
{ id, property } |
{ items }: the related nodes, in the relation's order |
get-url |
{ id } |
the node's URL |
parse-url |
{ url } |
{ id, culture } of the page the URL addresses, or null |
file-url |
{ id, property, adjustment? } |
a file's URL, or that of a resized or converted image |
content-link |
{ id, property?, adjustment? } |
{ link, url }: a stored link (#…) to a node or its file, to put in HTML |
content-links |
{ links } |
{ items }: what each stored link points at, or that it is missing or restricted |
node-meta |
{ id } |
what the store keeps beside the values: address, URL, dates, revision, culture, site, access, release, expiry |
meta-choices |
{ groups, collections }: the access groups and sites a node can be given |
|
timestamp |
the database's transaction timestamp, which moves with every write | |
commands |
every command the server knows |
schema gives a client what it needs to build an editor. Each type has its icon (as SVG), title,
description and canBeDraft. Each property has its kind and defaultValue, and one that holds
other nodes has display: List, Table, Inline, Cards or Thumbnail.
Writing nodes
| Command | Payload | Answers |
|---|---|---|
insert |
{ type, id?, values, ifNotExists?, draft? } |
the stored node; with draft, a draft readers do not see |
update |
{ id, values, force?, ifExists? } |
the node; only the properties given are written, and null resets one |
upsert |
{ type, id, values } |
the node, updated or inserted |
delete |
{ ids, ifExists? } |
{ deleted } |
relate, unrelate, set-relation, clear-relation |
{ id, property, to } |
true; to is one id or a list |
move-relation |
{ id, property, items, position \| offset \| before \| after } |
true; position is "top" or "bottom" |
set-relation-order |
{ id, property, items } |
true |
reset-property |
{ id, property } |
the node, the property back at its default |
add-to-property, multiply-property |
{ id, property, value } |
the node, a number changed in the store |
change-type |
{ id, type } |
the node, as a node of the new type |
update-address, update-display-name |
{ id, address }, { id, displayName } |
the node |
update-meta |
{ id, meta } |
the node, with its access, site, release, expiry or deleted changed |
transaction |
{ actions: [{ op, … }] } |
{ results }: each action's answer, in order |
Every write runs through NodeStore.Execute, so the store's transaction plugins run exactly as they
do for a write from C#.
transaction runs its actions in one transaction: all of them happen, or none. Each action is a
write command, named by op, with that command's payload. An action can use a node an earlier one
inserts:
{
"type": "transaction",
"payload": {
"actions": [
{ "op": "insert", "type": "INewsList", "id": "0f8fad5b-d9cb-469f-a165-70867728950e", "values": { "Name": "News" } },
{ "op": "insert", "type": "IArticle", "values": { "Name": "Hello world", "Parent": "0f8fad5b-d9cb-469f-a165-70867728950e" } }
]
}
}insertfills the properties it is not given with the type's defaults. In a context with aculture, the node's first version is in that culture. With acollection, the node is in that site.- A relation end takes an id, or a list of ids for a "many" end, and is set to exactly those.
update-meta'smetatakesreadAccess,editViewAccess,editAccess,publishAccess,collectionId,releaseUtc,expireUtcanddeleted. Null clears a value: an access group then comes from the type's or the database's default, and a node with nocollectionIdis in no site.
The content tree
The CMS interface's content tree runs on these commands. They read a one-to-many relation as parents
and children: the one named in relation (class name, full name or id), else the CMS setting
Content.Tree, else RTree.
| Command | Payload | Answers |
|---|---|---|
tree |
{ relation?, parent?, search?, root?, hidden? } |
{ relation, items, sites?, root? }: a parent's children, the top, or the nodes whose names hold search |
tree-path |
{ id, relation? } |
{ relation, path, site? }: the ids from the top down to the node's parent |
tree-move |
{ id \| ids, parent?, before?, after?, children?, relation? } |
{ id, ids }: the nodes moved |
tree-copy |
as tree-move |
a process that copies; its result is { id, copied, filesNotCopied } |
tree-count |
{ id \| ids, children?, relation? } |
{ nodes, warnAt }: how many nodes a copy would make, or a delete take |
tree-count-below |
{ ids, relation? } |
a process that counts what is below each node; its result is { counted, below } |
tree-delete |
{ ids, children?, relation? } |
{ deleted }, or { process } for a large delete |
tree-create |
{ type, name?, parent?, after?, relation?, values?, draft? } |
{ id }: a new node in the tree, made as insert makes it |
- Items are
{ $id, $type, $displayName, $children, $status },$childrencounting the children the tree shows. A search result also has$path, the way down to it. - Sites. When sites (
ISite) can be in the relation, as inRTree, the tree is one site's:rootpicks it. Only a site can go at the top. - Hidden pages. A page whose
ShowInTreeis off is left out, with everything below it.hidden: truelists them too, marked$hidden. - Moving. A node goes into
parentas its last child,beforeoraftera sibling, or to the top. Withchildren, the default, its branch goes with it. Without, its children take its place. - Copying. A copy has the node's values, access and files, and the links it can share: the "one"
end of a one-to-many relation, and many-to-many links. A copy that is cancelled or fails removes
what it made. Nothing limits its size, but the interface counts a copy first and warns past
CmsApiOptions.TreeCopyWarnAt(10 000 nodes; 0 never warns). - Deleting.
tree-deletetakes each node with everything below it, and is refused (403) when the user cannot read some of that. Withchildren: falseit deletes one node, and its children take its place. - Addresses. A node moved, copied or created gets an address the store makes unique, such as
news-2.tree-createmakes it from the node's name.
Revisions, cultures and sites
| Command | Payload | Answers |
|---|---|---|
revisions |
{ id } |
{ id, enabled, items }: the node's revisions in every culture |
create-revision |
{ id, revision?, type?, culture? } |
{ id, revision }: a copy of a revision, a draft unless type says otherwise |
set-revision-type |
{ id, revision?, type } |
{ id, revision }: the revision made Published, Preliminary (a draft) or Archived |
set-revision-culture |
{ id, revision?, culture } |
{ id, revision }: the revision moved to another culture, "" for none |
delete-revision |
{ id, revision } |
{ id, revision }; 409 for the node's last revision |
cultures |
[{ code, name, englishName }]: the cultures content can be written in |
|
add-culture |
{ code, site? } |
{ code, name }; with site, the culture is also one of the site's languages |
sites |
{ supported, types, items }: the sites the user can read, each with its cultures |
|
history |
{ id, revision?, max? } |
{ id, revision, items, more }: a revision's earlier versions, newest first |
history-version |
{ id, at?, revision? } |
the node as the version at (from history) had it |
history-restore |
{ id, at, revision? } |
{ id, revision, restored }: that version's values, written back |
preview |
{ instance, revisions, versions?, now? } |
{ instance, parameter } (Previews of drafts) |
revision can be left out for a node with one version. Publishing a revision archives the culture's
published one. history-restore writes values only, not files, passwords or relations, into the
revision the context's revisions names.
// a Norwegian draft, copied from the English revision; answers the new revision's id
{ "type": "create-revision", "payload": { "id": "…", "revision": "…", "culture": "nb-NO" } }
// publish it
{ "type": "set-revision-type", "payload": { "id": "…", "revision": "…", "type": "Published" } }File properties
| Command | Payload | Answers |
|---|---|---|
file-info |
{ id, property } |
the file as the user reads it, or null |
file-copy |
{ id, property, from: { id, property } } |
the file: another node's file, copied into this property on the server |
file-delete |
{ id, property } |
true: the file is taken out of the property |
A file is never a value, and update refuses one. Upload it with
POST {Path}/upload?id=…&property=…&name=…, the file as the body, up to
CmsApiOptions.MaxUploadBytes (1 GB). Add context=, a payload context as JSON, to upload into a
particular revision; the file commands use the context's revision too. file-copy sends no bytes
through the client. file-delete leaves the stored file in place, as another revision or a copy of
the node may hold it, and the store's clean-up of unreferenced files removes it later.
Processes and maintenance
| Command | Payload | Answers |
|---|---|---|
processes |
{ items }: the user's processes, oldest first |
|
process-pause, process-resume, process-cancel |
{ id } |
the process: held at its next checkpoint, let go on, or stopped |
process-dismiss |
{ id } |
the finished process, forgotten |
maintenance |
{ canRun, tasks }: the maintenance tasks, each { id, title, description, asks, warning } |
|
maintenance-run |
{ task, fix?, site?, culture? } |
a process that checks, or with fix also repairs |
The process commands answer 404 for another user's process, and 400 for one that cannot do what is
asked. maintenance-run is for administrators (403), runs one task at a time per user (409), and
needs the site and culture the task asks for. Its result counts the nodes it checked,
found, fixed and failed on, and items names the first 100 found, each with its problem.
Processes shows how to write a process, and Sites describes the tasks.
Editing together
| Command | Payload | Answers |
|---|---|---|
collab-join |
{ nodeId, revision?, connectionId, participant, live? } |
{ first, version, participants }: the caller has the node open |
collab-live |
{ nodeId, revision?, participant, live } |
{ first, version, participants }: starts or stops editing together |
collab-leave |
{ nodeId, revision?, participant } |
null: the caller has closed the node |
collab-send |
{ nodeId, revision?, participant, messages: [{ data, to? }] } |
null: the messages passed to the other live participants |
Each node blade of the CMS interface joins its node's room, so it knows who else has the node open.
A participant is an id the client chooses. connectionId is the id the event stream sent in
connected. A node with revisions has a room per revision.
first is true for a participant that went live while no one else was: it brings the document. The
server keeps no document. It passes the messages (base64, at most 8 MB per send) to the other live
participants, or to the one named in to, and never back to the sender's connection. The CMS
interface sends Yjs updates this way. Participants leave by themselves when their stream closes.
Skins, forms and releases
| Command | Payload | Answers |
|---|---|---|
skins |
{ default, skins, canEdit, ai }: the installation's own skins of the interface, and its default |
|
skin-save, skin-delete, skin-default |
{ skin }, { key }, { key } |
the skin saved; null; { default } |
skin-logo |
{ data } |
{ logo }: an uploaded logo picture, the value a logo token takes |
skin-generate |
{ prompt, tokens, current } |
an unsaved skin made by the AI; 503 without an AI |
form-layout |
{ type, layout } |
a type's form layout, saved; null for the default form |
ui-release, ui-releases |
the interface release served and chosen; the releases to choose from | |
ui-release-set |
{ channel, version } |
what the installation serves after the change |
cms-modules |
{ modules, interfacePlugins } (chapter 18) |
The commands here that change something are for administrators. Every skin value is checked before it is saved (Skins), and a skin cannot take a built-in skin's key.