Automation API

Protocol version 1

The Automation API is intended for external automation, not a modding or plugin platform yet. The surface may grow in future releases.

External scripts and tools can drive Daggermap over a local newline-delimited JSON (NDJSON) Automation API. Enable it in Settings → General → Automation.

Automation API settings in the General tab of Settings
Hover or tap the Automation section and fields for a quick guide.

Enable Automation API

Off by default. Turning this on makes the app listen on a local TCP port for incoming connections.

Automation port

TCP port for incoming NDJSON connections (default 8765).

Allow remote connections

When unchecked, the API listens on localhost only. Enable this only if you need to connect from another machine on your network and understand the exposure. There is no authentication — anyone who can reach the port can control the running app.

Automation status

Shows the bound address when the API is listening (for example 127.0.0.1:8765).


Protocol

Property Value
Transport TCP
Framing One JSON object per line (\n-terminated; \r ignored)
Default bind 127.0.0.1:8765
Remote bind 0.0.0.0:<port> when Allow remote connections is on
Auth / TLS None
Push / events None — request/response only
Protocol version 1 (advertised by hello)

Environment overrides (native app):

Variable Effect
DAGGERMAP_GOLEM Any non-empty value other than 0 forces the API on
DAGGERMAP_GOLEM_PORT Overrides the listen port

Semantics of success

A successful action or batch reply means the request was accepted for dispatch, not that the mutation has finished. On desktop, actions are queued and drained a few per frame. To confirm effects, follow up with a query (for example wait until golem.action_queue_count is 0) or wait a short moment.

Limits

Limit Value
Max request line 64 KiB
Concurrent clients 8
Action queue 256
Batch size 64 sub-requests
Drain per frame 8 actions
add_tokens per call 64

Discover live limits and registered action ids via hello.


Message envelope

Every request is a JSON object. Every response is a JSON object on its own line.

Request

Field Type Required Description
intent string yes hello | query | action | batch
id string no Correlation id; echoed on the response when present
(intent fields) See each intent below

Response

Success:

{"id":"1","ok":true,"result":{}}

Failure:

{"id":"1","ok":false,"error":"missing action_id"}
Field Type Notes
ok boolean Always present
result object | array | … Present on success (may be {})
error string Present on failure — human-readable, not a numeric code
id string Echoed only if the request included id

Errors are strings such as invalid JSON, unknown intent, path not found, action queue full, or action-specific validation messages (move_token requires id, no selection, …). The last failure is also mirrored under query path golem.last_error.


Quick start

  1. Enable Settings → General → Automation.
  2. Open a TCP connection to 127.0.0.1:8765.
  3. Write one JSON object followed by \n; read one JSON line back.

Wire example (send the left side; receive the right):

→ {"id":"1","intent":"hello"}
← {"id":"1","ok":true,"result":{"protocol":1,"app":"daggermap",…}}

→ {"id":"2","intent":"action","action_id":"set_viewport","args":{"zoom":1}}
← {"id":"2","ok":true,"result":{}}

→ {"id":"3","intent":"query","path":"session.viewport"}
← {"id":"3","ok":true,"result":{"origin_x":0,"origin_y":0,"zoom":1}}

Any language that can open a TCP socket and encode/decode JSON works. Treat the stream as NDJSON — do not assume one recv equals one message.


Intents

hello

Capability advertisement. Optional, but recommended as the first call.

Request

{"id":"1","intent":"hello"}

Result fields

Field Type Description
protocol number Wire protocol version (1)
app string "daggermap"
app_version string Optional; may be omitted
intents string[] ["hello","query","action","batch"]
features string[] e.g. validate, batch, depth, path, budgeted_drain
limits object action_queue, batch, drain_per_poll
actions string[] Every registered action_id

query

Read a filtered snapshot of app state. Queries run immediately (read lane).

Request

Field Type Required Description
path string no Dot path into the snapshot; omit or "" for the whole root
depth number no Truncate nested depth; omit or -1 = full; 0 strips nested objects/arrays
{"id":"q1","intent":"query","path":"session.viewport","depth":2}

Root keys (built only as needed for the requested path):

Path Contents
golem Automation listener status and queue depth
session Viewport, selection, document meta, grid, scene; at depth ≥ 2 also UI actions catalog and multiplayer/net
document Saved document JSON; at depth ≥ 2 also tokens_by_id

Unknown top-level paths return "path not found".

golem

Field Type Description
enabled boolean Automation preference (or env override) wants the listener on
active boolean Listening and bind succeeded
env_override boolean Forced on via DAGGERMAP_GOLEM
port number Configured port
listening boolean Socket is listening
bind_failed boolean Port could not be bound
client_connected boolean At least one client connected
client_count number Connected clients
request_count number Requests handled since start
action_queue_count number Accepted actions not yet applied
action_queue_cap number Queue capacity
drain_per_poll number Max actions drained per frame
bulk_insert_pending number Tokens still waiting in the bulk insert queue
bulk_insert_cap number Bulk insert capacity
last_error string Last failure message (or "")

session (depth ≥ 1)

Path Shape
session.viewport {origin_x, origin_y, zoom}
session.selection string[] of token UUID7 ids
session.document_path string
session.document_open boolean
session.window_width / session.window_height number (framebuffer pixels)
session.active_scene_id string
session.measurements_count number
session.background {loaded, tiled, width, height, asset_hash}
session.grid {cell_size, visible, style, line_size, origin:{x,y}, detect:{…}}
session.scene {projection_mode}0 top-down, 1 isometric

At depth ≥ 2:

Path Shape
session.actions [{id, label, section, description?}] — UI / Controls catalog
session.net Multiplayer and asset-sync snapshot (room_id, broker_url, state, kind, peer/sync counters, downloads, …)

document

Document save payload (schema_version, scenes, assets, drawings, …). When depth is omitted/-1 or ≥ 2, includes:

"tokens_by_id": {
  "<uuid7>": {
    "id": "<uuid7>",
    "path": "…",
    "world_position": {"x": 0, "y": 0},
    "scale_x": 1,
    "scale_y": 1,
    "z_order": 0,
    "name": "",
    "fields": {}
  }
}

Prefer narrow paths (session.selection, document.tokens_by_id.<id>) over dumping the whole root when scripting.


action

Validate and enqueue (or, on web, immediately run) a single mutation.

Request

Field Type Required Description
action_id string yes Registered action name
args object no Action parameters
{"id":"a1","intent":"action","action_id":"add_token","args":{"path":"/path/to/token.png","x":0,"y":0}}

Success result: {}
Common errors: missing action_id, unknown action_id, action queue full, or a validator string.


batch

Send up to 64 nested requests in one round-trip. Nested intents may be hello, query, action, or nested batch.

Request

{
  "id": "b1",
  "intent": "batch",
  "requests": [
    {"id": "a1", "intent": "action", "action_id": "select_all_tokens"},
    {"id": "q1", "intent": "query", "path": "session.selection"}
  ]
}

Success result: { "responses": [ <envelope>, … ] }

The outer envelope is ok: true even when some sub-requests fail — inspect each entry in responses.


Actions

Two families share the same action intent:

  1. Automation actions — purpose-built for scripting (tokens, viewport, draw, net, …).
  2. UI actions — the same ids shown under Settings → Controls, so anything bindable in the app can be invoked over the API.

Call hello (or query session.actions at depth ≥ 2) for the live catalog.

Coordinates

Convention Used by
World x/y (default for placement & draw) add_token, add_tokens, move_token, draw_*, measure_*
Screen pixels pan_view, zoom_*, select_token_under_cursor; add_token / add_tokens when space is "screen"
Point pairs Arrays [x, y] under named keys (start/end, or aliases a/b)

Colors are strings resolved like the in-app pen (named colors or #RRGGBB / #RRGGBBAA where supported).

Document & viewport

action_id Args Notes
open_document path (string, required)
save_document_as path (optional) No path → no-op
set_active_scene id (UUID7 string) and/or index (0-based integer) At least one required. index follows document.scenes / sort_order order. If both are set they must name the same scene. Does not persist the active scene back to the save file.
load_background path (required)
clear_background
detect_grid Needs a non-tiled background; poll session.grid.detect
set_viewport origin_x?, origin_y?, zoom? (> 0) Partial updates allowed
set_projection_mode projection_mode 0|1 and/or isometric bool Needs an active scene
set_gc_policy max_state_bytes?, tombstone_retention_days?, run_gc? (default true) At least one policy field

Tokens

action_id Args Notes
add_token path or asset_hash; optional x,y; optional space="screen" No xy → spiral place; selects the new token
add_tokens one of: dir; positions + shared path/hash; tokens[] Max 64; see shapes below
move_token id, x, y World coords; id is UUID7 string
set_token_scale id + (scale or scale_x+scale_y)
copy_tokens / cut_tokens Requires selection
paste_tokens Requires clipboard

add_tokens shapes:

{"dir": "/path/to/folder"}
{"path": "/tok.png", "positions": [[0, 0], [72, 0]]}
{"tokens": [{"path": "/a.png", "x": 0, "y": 0}, {"path": "/b.png", "x": 72, "y": 0}]}

Prefer add_tokens over many tiny add_token calls when dumping assets in.

Draw & measure

World-space strokes. Point-pair shapes accept primary keys or a/b aliases.

action_id Args
draw_freehand points (≥ 2 × [x,y]); optional color, thickness (default 6)
draw_rectangle top_left/a + bottom_right/b; optional outline_color, fill_color, thickness
draw_line start/a + end/b; optional colors / thickness
draw_circle center/a + edge/b; optional colors / thickness
draw_cone apex, base_mid; optional outline_color, thickness, angle_deg (default 90)
draw_freehand_commit points or pattern (rdp_zigzag|sine) + point_count; optional zoom
clear_my_drawings / clear_all_drawings / clear_history
measure_line start/a + end/b
measure_freehand same point sources as freehand commit helpers
clear_measurements

Multiplayer

action_id Args Notes
net_configure optional broker_url, room_id, display_name, room_key, kind, peer_candidate, stun_host, stun_port Empty room_key clears E2EE passphrase
net_connect same as configure; needs room_id (here or previously)
net_disconnect / net_retry
net_set_peer_candidate peer_candidate
cancel_bg_load / cancel_asset_sync

UI actions (Controls)

Every id under Settings → Controls is registered as an action_id. Examples:

Section Ids
Session undo, redo, open_document, save_document_as
UI back, select, toggle_hint_overlay, toggle_settings, toggle_drawer, open_action_assets, open_action_map, open_action_scenes, open_action_draw, open_action_measure, open_action_grid
Navigation pan_view, zoom_in, zoom_out, pinch_zoom, reset_view, fill_view, fit_view_to_content
Tokens select_token_under_cursor, select_all_tokens, clear_selection, open_token_details, set_token_scale_up, set_token_scale_down, remove_token, copy_tokens, cut_tokens, paste_tokens, move_token_north / _south / _east / _west
Draw set_shape_freehand, set_shape_rectangle, set_shape_cone, set_shape_line, set_shape_circle, clear_all_drawings, clear_my_drawings
Menu toggle_draw_mode, toggle_measure, cycle_draw_shape, toggle_snap_to_grid, toggle_background_tiled, toggle_isometric_view, toggle_grid_adjust_handles, toggle_video_controls

Args for selected UI actions

Action Args
pan_view phase: "down" | "move" | "up" (default "down"); optional screen x,y (default window center). move requires a prior down.
pinch_zoom scale (number); optional screen x,y
zoom_in / zoom_out optional screen x,y (focal point)
select_token_under_cursor optional screen x,y
move_token_* optional steps (default 1); requires selection

Selection-required actions fail validation with "no selection": move_token_*, remove_token, set_token_scale_up / _down, cut_tokens, copy_tokens.


Examples

Place a token and read it back

→ {"id":"1","intent":"action","action_id":"add_token","args":{"path":"/tmp/goblin.png","x":144,"y":72}}
← {"id":"1","ok":true,"result":{}}

→ {"id":"2","intent":"query","path":"session.selection"}
← {"id":"2","ok":true,"result":["018f…"]}

→ {"id":"3","intent":"action","action_id":"move_token","args":{"id":"018f…","x":216,"y":72}}
← {"id":"3","ok":true,"result":{}}

Draw a measured line

→ {"id":"4","intent":"action","action_id":"draw_line","args":{"start":[0,0],"end":[144,0],"outline_color":"#c44","thickness":4}}
← {"id":"4","ok":true,"result":{}}

Validation failure

→ {"id":"5","intent":"action","action_id":"move_token","args":{"x":1}}
← {"id":"5","ok":false,"error":"move_token requires id"}

Wait for the write queue to drain

→ {"id":"6","intent":"query","path":"golem.action_queue_count"}
← {"id":"6","ok":true,"result":0}